I come from a Python background, but I started trying to learn Angular and I'm really having trouble. Working between components is confusing to me and I can't figure it out. I made a good example that I think if someone helped me with it would go along way towards understanding Angular.
I just have two components: a "header" component and an app component. In the header component, I ask for the user's name and they click a button, and then it should show "Hello {{name}}" in the next component. I cannot get it to work to say the least and it's really frustrating. The Header part seems to work okay, but it's just not communicating with the other component at all. Neither the button part or the "name" part are working so I am clearly misunderstanding something I need to do when it comes to listening from the parent component.
Here is my Header HTML:
Name: <input type="text" id="userInput" value="Joe">
<button (click)=showName()>Show More</button>
Here is my Header TS:
import { Component, OnInit, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
bodyDiv = false;
inputName = '';
#Output() buttonClicked = new EventEmitter();
constructor() { }
ngOnInit() {
}
showName() {
console.log('showName clicked.');
this.bodyDiv = true;
this.inputName = document.getElementById('userInput').value;
console.log(this.inputName);
console.log(this.bodyDiv);
this.buttonClicked.emit(this.bodyDiv);
this.buttonClicked.emit(this.inputName);
}
}
Here is the main Component's HTML:
<app-header (buttonClicked)='showNextComponent($event)'></app-header>
<p *ngIf="![hiddenDiv]" [inputName]="name">Hello {{ name }} </p>
Here is the main component's TS:
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
hiddenComponent = true;
title = 'show-button';
showNextComponent() {
console.log('Button clicked.');
this.hiddenComponent = false;
console.log(this.hiddenComponent);
}
}
So who can show me what I'm doing wrong and help figure out Angular a little better? :) Thank you!
replace showName function with below code :
showName() {
console.log('showName clicked.');
this.bodyDiv = true;
this.inputName = document.getElementById('userInput').value;
console.log(this.inputName);
console.log(this.bodyDiv);
this.buttonClicked.emit(this.inputName);
}
replace below code in your main component.
name:string
showNextComponent(value:string) {
this.name = value;
}
replace below code in your html :
<app-header (buttonClicked)='showNextComponent($event)'></app-header>
<p *ngIf="name">Hello {{ name }} </p>
Please let me if you have any question and I would suggest try to use ngmodel or something else instead of directly communicating with the DOM.
Here is a slightly modified and working sample: https://stackblitz.com/edit/angular-jhhctr
The event emitter in the header component emits the name (string) which is the $event in showNextComponent($event). You have to capture this in the main component and assign it to a local variable to be able to use it in the main component's template as {{name}}
[inputName]="name" is incorrect. You can pass values like that to angular components not to actual HTML DOM elements.
There are couple of ways to communicate from one component to another in angular - Using #Input()in your child component will expects an input from parent component and #Output() from your child component will emit an event from the child component
So in your case if you want to pass a value from parent to child you need to use input property or decorator on your child property - I will provide you the code but just go through proper guidance from the link provided this will make you to create better angular applications https://angular.io/guide/component-interaction
First you need to swap your components your header component should be your parent and the child component will be your main component - if you want to work in the same way just move your codes vice versa
Header html
Name: <input type="text" id="userInput" name='userInput' [(ngModel)]='inputName' value="Joe">
<button (click)=showName()>Show More</button>
<div [hidden]='bodyDiv'>
<app-header [bindName]='inputName'></app-header>
</div>
Header Component
import { Component, OnInit, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
bodyDiv = true;
inputName = '';
constructor() { }
ngOnInit() {
}
showName() {
bodyDiv = false;
}
}
Main Component Html
<p>Hello {{ bindName }} </p>
Main component ts
import { Component, Input } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
#Input()
bindName: string;
}
In your header component the inputName property will be binded using two way data binding where i used [(ngModel)]='inputName' so whatever you enter in the input text it will be updated in your inputName property
Now we need to do only one thing just to show your child component with any event - so when the button is clicked the div with [hidden] property will be false and it will be displayed and as we pass the inputName to the child Component it will be updated
And finally the child component will be displayed and the input written in the text will be updated in the child component - when the child component html displays the bindName will be updated and there will be result you expected
That's all I think this should work well - Try this and let me know - Thanks Happy coding !!
Don't forget to look into the link above where you can see many types of component interactions
Related
I am trying to write an application and I don't know how to change a variable value that is defined in parent component and accessed in child component.
Below is the code for child component:
This is the child component HTML markup:
<p>Child Component Works!</p>
<h2>{{name.name1}}</h2>
<h4>{{name.Money1}}</h4>
<h2>{{name.name2}}</h2>
<h4>{{name.Money2}}</h4>
Child component Typescript code:
import { Component, EventEmitter, OnInit, Input } from "#angular/core";
#Component({
selector: 'app-child-component',
templateUrl: './child-component.component.html',
styleUrls: ['./child-component.component.css']
})
export class ChildComponentComponent implements OnInit {
#Input() name;
constructor() { }
ngOnInit() {
}
}
Parent-component.html markup:
<p>
Parent Component
</p>
<app-child-component [name]="data"></app-child-component>
<button>
Send Money To Jack
</button><br><br>
<button>
Send Money To Jill
</button>
Parent-component.ts code:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-parent-component',
templateUrl: './parent-component.component.html',
styleUrls: ['./parent-component.component.css']
})
export class ParentComponentComponent implements OnInit {
data = {
name1:'jack',
Money1:10,
name2:'Jill',
Money2:'15'
}
constructor() { }
ngOnInit() {
}
}
This is the whole code. And below is the screenshot of output:
This is the output. I want some function that when user clicks on 1st Button then money1 variable gets incremented by 10 and when user clicks on second button then money2 variable gets incremented by 10:
That structure for displaying the data is not too good, and the scenario is somewhat sketchy, but if you're not going to change things atm, here you are:
add click functions to buttons:
<button (click)="incrementJack()">Send Money To Jack</button>
<button (click)="incrementJill()">Send Money To Jill</button>
define them in parent ts:
incrementJack(){
this.data.Money1 += 10;
}
incrementJill(){
let amount = parseInt(this.data.Money2) + 10;
this.data.Money2 = amount.toString();
}
Note that the money1 is number and money2 is a string, so this cod is keeping their types.
I have created a component to reuse the mat-progress-spinner from angular material. I need this in order to avoid putting for every single page the same code. Here is the code that is working:
<div id="overlayProgressSpinner">
<div class="center">
<mat-progress-spinner
style="margin:0 auto;"
mode="indeterminate"
diameter="100"
*ngIf="loading">
</mat-progress-spinner>
</div>
</div>
It is simple. Only to set "loading" as true or false.
What did I do?
I put above code inside a custom component. Now it is like so:
<app-progress-spinner></app-progress-spinner>
its HTML code is the same and its TS code is as a follows:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-progress-spinner',
templateUrl: './progress-spinner.component.html',
styleUrls: ['./progress-spinner.component.scss']
})
export class ProgressSpinnerComponent implements OnInit {
loading = false;
constructor() { }
ngOnInit() {
}
public isLoading(value: boolean) {
this.loading = value;
}
public changeSpinnerCSSClass() {
const htmlDivElement = (window.document.getElementById('overlayProgressSpinner') as HTMLDivElement);
if (this.loading) {
htmlDivElement.className = 'overlay';
} else {
htmlDivElement.className = '';
}
}
}
when the property "loading" belongs to the current component, I can show and hide the "mat-progress-spinner" component. Otherwise, when it belongs to "app-progress-spinner" it is set but it is not being displayed. The code that I am trying to make it visible is as follows:
this.progressSpinner.isLoading(false); // it is set, but it does not work.
this.progressSpinner.changeSpinnerCSSClass(); // it works
it appears that *ngIf="loading" cannot be set by using the approach the works if the logic behind belongs to the current component.
How to achieve this?
You need to create an input in your ProgressSpinnerComponent. To do that, add the #Input() decorator before the property loading:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-progress-spinner',
templateUrl: './progress-spinner.component.html',
styleUrls: ['./progress-spinner.component.scss']
})
export class ProgressSpinnerComponent implements OnInit {
#Input() loading = false;
So anywhere you need to use the app-progress-spinner you do:
<app-progress-spinner [loading]="loading"></app-progress-spinner>
Note: The loading variable assigned to the input loading belongs to the component that contains theapp-progress-spinner.
This happens because every component have it own scope, meaning that it have no access to external world unless you create an input or output in order to receive or send data. There's also the ngModel that can be used for bi-diretional data, but not recommend in most cases.
I am new to Angular 7 (2+) & trying my hands on #Input & #Output. However, passing data from Parent to Child component via #Input is understood & in place.
However, very basic on the other hand passing data from Child to Parent component via using #Output concept is understood & but the implementation is not getting right.
Here is what I am trying. When a button is clicked in the
Child component, a property in the parent component should be
converted to Upper case & displayed.
ChildComponent.ts
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
})
export class ChildComponent implements OnInit {
#Input('child-name') ChildName: string;
#Output() onHit: EventEmitter<string> = new EventEmitter<string>();
constructor() { }
ngOnInit() {}
handleClick(name): void {
this.onHit.emit(name);
}}
ChildComponent.html
<h1> child works! </h1>
<button (click)="handleClick('eventEmitter')"> Click me! </button>
ParentComponent.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
title = 'my-dream-app';
customerName: string = "";
catchChildEvent(e) {
console.log(e);
}}
ParentComponent.html
<div style="text-align:center">
<app-child [child-name]="HelloChild"></app-child>
//Trying to bind to Custom event of child component here
<b (onHit)="catchChildEvent($event)">
<i> {{customerName}} </i>
</b>
No error in console or binding
From the above snippet, when the button in Child Component is clicked the parent Component's property CustomerName should get the value & displayed?
Example: https://stackblitz.com/edit/angular-3vgorr
(onHit)="catchChildEvent($event)" should be passed to <app-child/>
<app-child [child-name]="HelloChild" (onHit)="catchChildEvent($event)"></app-child>
You are emitting event from app-child component so attach the handler for app-child component to make it work.
<div style="text-align:center">
<app-child (onHit)="catchChildEvent($event)" [child-name]="HelloChild"></app-child>
//Trying to bind to Custom event of child component here
<b>
<i> {{customerName}} </i>
</b>
And within the ts file update value of cutomerName property.
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
title = 'my-dream-app';
customerName: string = "";
catchChildEvent(e) {
this.customerName = e;
}
}
You should move (onHit)="catchChildEvent($event)" to app-child in parent html:
<app-child [child-name]="HelloChild"
(onHit)="catchChildEvent($event)>
</app-child>
i'm struggling about this problem and can't figure out.
I simply need to show a popup div situated in the page clicking from a menu entry in my navbar.component.
I added a property "show" in my popup which prints the "show" class on my div using the ngClass (with if) directive. I can get this working if the action button is inside my popup component but i cannot print the show class clicking on another component. The property in the Object get updated but the class is not printed. I'm using angular 4 with ng-bootstrap. I tried both with services and with parent/child emit event.
This is is my situation:
app.component.html
<app-nav-bar></app-nav-bar>
<app-login></app-login>
<router-outlet></router-outlet>
<app-footer></app-footer>
navbar.component.html
...
<button class="dropdown-item" (click)="showPopup()">LOGIN</button>
...
navbar.component.ts
import {Component, EventEmitter, Input, OnInit, Output} from '#angular/core';
#Component({
moduleId: module.id,
selector: 'app-nav-bar',
templateUrl: 'navbar.component.html',
styleUrls: ['./navbar.component.css'],
})
export class NavbarComponent implements OnInit {
#Output() show = new EventEmitter<boolean>();
ngOnInit() {
}
showPopup() {
this.show.emit(true);
}
}
login.component.html
<div id="wrapper-login-popup" class="fade-from-top" [(class.show)]="show">
<div id="container-login-popup">
<div class="row">
<div class="col-sm-12 text-center">
<img id="popup-bomb" src="assets/images/bomb.png" alt="bomb"/>
<img id="popup-close" class="close-icon" src="assets/images/close.png" alt="close"
(click)="closePopup()"/>
</div>
</div>
</div>
</div>
login.component.ts
import {Component, Input, OnInit} from '#angular/core';
import {AuthService} from '../services/auth.service';
import {IUser} from './user';
#Component({
selector: 'app-login',
templateUrl: 'login.component.html',
styleUrls: ['login.css']
})
export class LoginComponent implements OnInit {
private username: string;
private password: string;
#Input() show: boolean = false;
constructor(private AuthService: AuthService) {
}
ngOnInit() {
}
login() {
...
}
showPopup() {
console.log(this); //Show is false
this.show = true;
console.log(this); //Show is true but does not trigger the show class
}
closePopup() {
this.show = false;
}
}
The issue here is that your nav-bar and login components are siblings and can't directly communicate with each other. You have show as an output of navbar and as an input of login, but you haven't connected the dots.
You need to update your app.component to connect them.
export class AppComponent implements OnInit {
show = false;
onShow() { this.show = true; }
}
and in the template:
<app-nav-bar (show)="onShow()"></app-nav-bar>
<app-login [(show)]="show"></app-login>
There's a lot of two way binding going on here which works for something simple liek this, but generally it's a bad idea as it leads to unmaintainable code. You should choose one owner of the show variable and force all changes to it through him. In this case the app component is the most logical owner, so I'd change the login component to emit an event that changes the show variable in app component adn remove all 2 way bindings, but in a bigger app, you may even want a separate service that manages hiding/showing pop ups. This eliminates the need for the sending a message up and down your component tree, you can inject the service where it's needed.
As another commenter mentioned, you also should be using ngClass for class manipulation like
[ngClass]="{'show':show}"
a service based solution would look like
import {Subject} from 'rxjs/Subject';
#Injectable()
export class PopUpService {
private showPopUpSource = new Subject();
showPopUp$ = this.showPopUpSource.asObservable();
showPopUp() { this.popUpSource.next(true); }
closePopUp() { this.popUpSource.next(false); }
}
Then you provide in app module or at app component level:
providers:[PopUpService]
make sure you don't re provide this later, as you only want one copy to exist so everyone shares it.
then inject into both components, and have them call the services close or show pop up methods.
then in the login component you bind to the popUp$ observable like
constructor(private popUpSvc:PopUpService){}
show$;
ngOnInit() { this.show$ = this.popUpSvc.showPopUp$; }
showPopUp() { this.popUpSvc.showPopUp(); }
closePopUp() { this.popUpSvc.closePopUp(); }
and in the template subscribe w async pipe like
<div id="wrapper-login-popup" class="fade-from-top" [ngClass]="{'show': (show$ | async) }">
The reason for using the async pipe is garbage collection managemetn is simpler. If you don't use async, you need to garbage collect manually in ngOnDestroy by calling unsubscribe(), otherwise your subscriptions will keep stacking up. There is also a more nuanced benefit in that the async pipe triggers change detection, but this only becomes important if you start using onPush change detection for performance optimization.
I'm trying to learn Angular 2, and I'm running into a weird issue while following this documentation: https://angular.io/docs/ts/latest/guide/user-input.html for binding button clicks to a method on my controller.
I started off with their click event binding example. I created a simple app with the angular cli with ng new test-app and modified it so that it contains a single button that when I click, just adds a message to the page.
I have two components, the app component and the message components shown here:
message.component.html:
<p>{{message}}</p>
message.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-message',
templateUrl: './message.component.html',
styleUrls: ['./message.component.css']
})
export class MessageComponent implements OnInit {
message: string;
constructor() {
this.message = 'some initial message';
}
ngOnInit() {
}
}
app.component.html:
<button (click)="addMessage()">Click</button>
<app-message *ngFor="let message of messages"></app-message>
app.component.ts:
import { Component } from '#angular/core';
import { MessageComponent } from './message/message.component';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
messages = [];
addMessage() {
const newMessage = new MessageComponent();
newMessage.message = 'Something else entirely';
this.messages.push(newMessage);
}
}
When I run this with ng serve, the button appears as expected, but when I click the button, only the message some initial message appears despite being given a different value by my app controller. While doing a search I found a different way to do the one-way databinding by replacing the string interpolation with: <p [innerText]="message"></p> but the result is the same.
I'm kinda at a loss here as to why it won't display the updated value of Something else entirely.
MessageComponent component should take message as #Input:
export class MessageComponent implements OnInit {
#Input() message: string;
}
AppComponent should send this input to its child like
<app-message *ngFor="let message of messages"
[message]="message.message"></app-message>