Consider the following snippet of Parent's template:
<div *ngFor= "let event of events" >
<event-thumbnail [theEvent] = 'event'></event-thumbnail>
</div>
Also event-thumbnail component definition is:
export class EventThumbnailComponent{
intoroduceYourself(){
console.log('I am X');
}
}
In Parent component class, I want to iterate over all generated event-thumbnail elements, access the component beneath each, and call introduceYourself function on single one of them.
You want to use the #ViewChildren() decorator to get a list of all instances of a specific component type within the view:
class ParentComponent implements AfterViewInit {
#ViewChildren(EventThumbnailComponent)
eventThumbnails: QueryList<EventThumbnailComponent>;
ngAfterViewInit(): void {
// Loop over your components and call the method on each one
this.eventThumbnails.forEach(component => component.introduceYourself());
// You can also subscribe to changes...
this.eventThumbnails.changes.subscribe(r => {
// Do something when the QueryList changes
});
}
}
The eventThumbnails property will be updated whenever an instance of this component is added to or removed from the view. Notice the eventThumbnails is not set until ngAfterViewInit.
See the docs here for more information:
https://angular.io/docs/ts/latest/api/core/index/ViewChildren-decorator.html
Your child component should have #Input() theEvent to get access to the event you are passing. Then you can use the following lifecycle hook:
ngOnInit(){
introduceYourself(){
console.log('I am X');
}
}
Related
As we know , in angular parent to child communication happened using #Input decorator but how to pass dynamic data from parent to child.for example 'name' property is defined in parent and it's value changes dynamically and updated value of 'name' we are going to using in child component so how to achieve it without services.
you can watch your input changes in the child component with ngOnChanges lifecycle
in your child component you should implements OnChanges
ngOnChanges(changes: SimpleChanges): void {
for (let propName in changes) {
let change = changes[propName];
if (propName === "YOUR_INPUT_NAME") {
this.YOUR_INPUT_NAME = (change.currentValue);
}
}
}
You can make the use of service to get the updated value in child when it gets changed in parent.
For that you need to declare the variable in the service of type observable.
your myService.service.ts would be having the following code:
#Injectable()
export class myService {
myInput:BehaviorSubject<boolean> = new BehaviorSubject(false)
}
now from your parent component change the value of this myInput by using:
myServie.myInput.next(true)
and subscribe this variable in your child component by using:
myService.myInput.subscribe((data)=>{console.log(data)})
In both the parent and child component add the line private mySerivce:MyService in the arguments of constructor.
I have created a reusable component and using it twice inside a component. But I need two buttons, that can manipulate the component individually.
In my case the button for component1 should not update both the instance of the component.
I think I'm doing something wrong by design, but any suggestion will help me.
Stackblitz
Reusable Component:-
import { Component, OnInit,Input } from '#angular/core';
import { AppService } from '../app.service';
#Component({
selector: 'app-reusable',
templateUrl: './reusable.component.html',
styleUrls: ['./reusable.component.css']
})
export class ReusableComponent implements OnInit {
#Input() items:any;
constructor(
private service:AppService
) { }
ngOnInit() {
this.service.addFruit.subscribe(()=>{
this.items.unshift({fruit:'Blackberry'});
});
}
}
Usage:-
<button type="button" (click)="Add()">Add Component1</button>
<app-reusable [items]="fruitList1"></app-reusable>
<hr/>
<button type="button" (click)="Add()">Add Component2</button>
<app-reusable [items]="fruitList2"></app-reusable>
I want to update only one instance of reusable component at once.
Either instance 1 or 2.
You have to let the service know which component you are calling from.
Try the changes I made in the demo.
app.component.html
<button type="button" (click)="Add(1)">Add Component1</button>
<app-reusable [items]="fruitList1" [componentNumber]="1"></app-reusable>
app.component.ts:
Add(componentNumber:number){
this.service.addFruit.next(componentNumber);
}
reusable.component.ts:
#Input() componentNumber: number;
ngOnInit() {
this.service.addFruit.subscribe((x) => {
if (x == this.componentNumber)
this.items.unshift({ fruit: 'Blackberry' });
});
}
Working Stackbiltz
More cleaner approch would be simply pass the component instance and call related method so create a method in your reusable component something like
addFruit(){
this.items.unshift({fruit:'Blackberry'});
}
Modify your add method to get component as instance and call this method
Add(instance:ReusableComponent){
instance.addFruit();
}
Then add hash to seprate each instance and pass instance in method
<button type="button" (click)="Add(instance1)">Add Component1</button>
<app-reusable [items]="fruitList1" #instance1></app-reusable>
Working demo
Each instance of ReusableComponent is subscribed to the Subject addFruit. Clicking on the button will update Subject value which will trigger all subscriptions.
In order to avoid this, you will need to add a filter in a subscription which ignores values from other components by adding some when doing this.service.addFruit.next();. You can do filtering with RXJS filter operator. https://rxjs-dev.firebaseapp.com/api/operators/filter
Another idea is to create a subscription for each component in service and save them in some map/object in service. When a component requests a subscription from service it would add an entry to map which would be subjectId: new Subject(). You would return that new subject for the component to subscribe. Instead of doing next() directly you would call service method addNewFruit(subjectId: string, newFruit: string): void.
Map would be:
{
'firstId': Subject,
'secondId': Subject,
}
The most simple idea for this case is to use ViewChild and call method addFruit from the parent component.
Instead of having subscription of APP service in the reusable component , on click of button you should modified the input provided to your components. If you update the fruitList1 or fruitList2 at a time then it would not update the another instance of the component.
I've been studying Angular's lifecycle hooks while looking for a way to know when and which child components are loaded.
I see that ngAfterViewInit()
"Responds after Angular initializes the component's views and child
views."
Since ngAfterViewInit knows about the children, how could I get their identifying information as they (or after) they initialize?
Something like this pseudo code:
// ngAfterViewInit() - Respond after Angular initializes the component's views and child views.
export class AppComponent implements AfterViewInit {
ngAfterViewInit() {
console.log(‘Children should be loaded');
// loop through ngAfterViewInit
querySelector('body').classList.add(ngAfterViewInit[i].componentName);
}
}
sidenote: the goal is to add component names or selector names to <body>'s class list.
I have a angular 2 page where i need to show 2 different components using same array of data from external API. Parent is regular component, and child is shared among several other components using same functionality.
In parent component class i have output property declared:
public weatherList: WeatherForecast[];
#Output() public weatherListData: any;
Inside constructor of parent component, i populate weatherListData property with data from an external API
http.get(url)
.subscribe(result => {
this.weatherList= result.json() as WeatherForecast[];
this.weatherListData = this.weatherList;
});
and i'm using it inside parent template with success, something like: {{ weatherList.someValue }}
Also, inside parent component template, i have a call to a child component
<daily-temperature-chart [weatherListData]='weatherListData'></daily-temperature-chart>
In child component class i have declared property
#Input() weatherListData: any;
but, when i try to access weatherListData property in constructor, or init of child component, i get undefined result.
EDIT: I have played with console.log() and noticed that child component Constructor and OnInit() methods return before http.get() from parent component. Maybe this is problem, but i'm still new to angular and can't tell.
Can someone point me how to solve this?
You've a service call so you can't go for constructor or OnInit because component initialization is not dependent on your service call for this situation angular provides OnChanges whenever your input value is updated OnChanges fired.
ngOnChanges(changes: any){
console.log(changes);
console.log(this.weatherListData);
}
OnChanges passes as well an argument which informs about the current state and pervious state now you are able to use input values. If your components are bases on input and input is based on any other operation you can handle it in this block.
So i have this Component of a from with an #Output event that trigger on submit, as follows:
#Component({
selector: 'some-component',
templateUrl: './SomeComponent.html'
})
export class SomeComponent{
#Input() data: any;
#Output() onSubmit: EventEmitter<void> = new EventEmitter<void>();
constructor(private someService: SomeService) {}
submitForm(): void{
this.someService.updateBackend(this.data, ()=>{
this.onSubmit.emit();
});
}
}
I'm using an ngFor to create multiple elements of this Component :
<template let-data ngFor [ngForOf]="dataCollection">
<some-component [data]="data" (onSubmit)="doSomthing()"></some-component>
</template>
The last missing part is the service used on submitting:
#Injectable()
export class SomeService{
constructor() {}
updateBackend(data: any, callback: () => void): void{
/*
* updating the backend
*/.then((result) => {
const { errors, data } = result;
if (data) {
callback();
}
})
}
}
At the beginning of the submitForm() function, the this.onSubmit.observers is an Array containing one observer, like it should be.
As soon as it reaches the callback method, where the this.onSubmit.emit() is invoked, the this.onSubmit.observers is an Array containing ZERO observers.
I'm experiencing two very weird behaviors:
If i remove the actual calling to update the backend in SomeService.updateBackend it works perfectly fine, and the observers still is an Array containing one observer!
If i keep the actual calling to the backend BUT not using ngFor and displaying only one <some-element> it also works perfectly fine, keeping one observer in the this.onSubmit.observers within the callback scope!
Any idea what am i doing wrong?
Thanks in advance!
Update:
Thanks to #StevenLuke's comment about logging the ngOnDestroy of SomeComponent I found out that it is being destroyed before the emit.
Actually, the first thing it is doing when the SomeService.updateBackend finishes is Destroying all the instances of this component and recreate them!
This is what makes the observers change! Why would that happen?
If you provide a trackBy function in your *ngFor to identify items in your dataCollection, it will not destroy and init. Your template would be:
<some-component *ngFor="let data of dataCollection;trackBy:trackByFunction"
[data]="data" (onSubmit)="doSomthing()"></some-component>
And the trackByFunction would look like:
trackByFunction(index, item) {
return item ? item.id : undefined;
}
So even though an item in your dataCollection is a fresh object, if its id matches an id in the previous collection, *ngFor will update [data] but not destroy and init the component.
Thanks to #GünterZöchbauer comments I found out the case was that the data the ngFor is bound to was being replaced by a new instance as I updated the backend, hence, it rerendered it's child Components causing reinitializing (destory + init) of them, which made the instance of the Component to be overwritten.
In order to solve this issue i had to place the dataCollection in a separate service, getting it for the parent component ngOnInit, saving it from causing a rerender of the ngFor, and fetch its data again only after the execution of the Child Components ended
Hope it'll be helpful to somebody!