Do you have any idea about the strange problem below?
I am passing data from a parent to a child component that is returned from a service method returning data as Observable<DemoModel>. But, when child component is loading, the data is undefined and it is only filled after ngAfterViewInit (I also tried getting the data on this method, but the data is still undefined). So, I also tried to apply some ngOnchanges approach, but the problem is much more related to that the data retrieved from Parent Component is not ready while Child Component is loading (I also tried to use async, etc. instead of subscribe. How should I get data to make it ready while child component is loading?
Parent and Child Components are shown below:
Parent Comp
<child-component
[courses]="courses|async"
>
</child-component>
courses: any;
this.service.listCourses().subscribe((course: Course) => {
this.courses = course;
});
Child Comp
private courses: any;
#Input()
set data(data: any) {
this.courses.next(data);
}
myControl = new FormControl('');
ngAfterViewInit() {
// >>> THIS THROWS ERROR AS this.courses is undefined
this.myControl.setValidators([
Validators.required,
forbiddenNamesValidator(this.courses)
]);
}
I also tried to use some *ngIf in html, but as the this.courses parameter is used in the methods, it does not make any sense to check the data in html.
The problem may be caused by subscribe method, but I also tried to use promise (I am not sure if I used it properly).
There's a couple things wrong with your current implementation:
In your Parent component, courses is an array (I assume) not an observable - no need to use the async pipe
In your Child component you've named the input field data, and used a setter to call .next on a variable that should be an array - i.e. .next wont exist.
The below should fix your current implementation
Parent Comp
<child-component
[courses]="courses"
>
</child-component>
courses: any;
this.service.listCourses().subscribe((course: Course) => {
this.courses = course;
});
Child Comp
#Input() courses: any;
It's important to note that listCourses is asynchronous
What this means is that courses won't necessarily be guaranteed to have a value when ngAfterViewInit is called and will likely then throw a similar error.
What I can suggest to solve this is the following:
<child-component
*ngIf="courses?.length"
[courses]="courses"
>
</child-component>
You won't then have to wait for ngAfterViewInit, and instead can just wait for ngOnInit.
ngOnInit(): void {
this.myControl.setValidators([
Validators.required,
forbiddenNamesValidator(this.courses)
]);
}
Comments
Passing a list from parent to child, should I use an observable/promise/array etc?
That's entirely up to you, I prefer using the async pipe when dealing with observables, because then I don't need to worry about unsubscribing from my subscriptions.
<child-component
[courses]="courses | async"
>
</child-component>
courses = this.service.listCourses()
I think there is no need to use get/set for the courses in Child Component as the list will not be changed
You don't necessarily need to even use a get/set when engaging with data that changes. Angular will update the #Input data for you, so you don't need worry about using a get/set unless you explicitly need that functionality.
Should I call the this.myControl.setValidators([]} and the filter method in the onInit or afterViewInit
There's no need to shift setting the validator into your afterViewInit, you don't need to wait for your Component's View to be initialized before you set the validator.
1st way: Add ngIf to check whether you have data or not.
<child-component [courses]="courses" *ngIf="courses.length > 0"> </child-component>
2nd Way : If you want use async then don't subscribe it in your component.
<child-component [courses]="courses$ | async" *ngIf="(courses$| async)?.length > 0"> </child-component>
Component:
courses$: Observable<any>;
this.courses$ = this.service.listCourses().pipe(shareReplay());
Related
In my Angular 9 project I have 2 components which are siblings and the parent component. On change in component A, I emit a value and it's set in the parent component and calls a method in component B. The method in component B emits another value and it's set in the parent component. The on change in component A continues, but the emitted value from component B that is set in the parent component (which is an input in component A) is not changed. I don't know why it's not the input for component A does not change even though the parent updates the value.
Parent Component
setSomeNum(someNum: number) {
// this is run on someNumberEmitter in Component A
this.num = someNum;
if (this.viewChildComponentB) {
this.viewChildComponentB.remove(someNum);
}
}
setSomeOtherNum (someOtherNum: number) {
// this is run on someDiffNumEmitter in Component B
this.otherNum = someOtherNum
}
Component A
componentAOnChange(someNum: number) {
this.someNumberEmitter.emit(someNum);
// this.inputFromComponentB is the original value instead of the one emitted in Component B (this.someDiffNum)
this.someService.applyCalc(someNum, this.inputFromComponentB);
}
Component B
remove(someNum: number) {
this.someDiffNumEmitter.emit(this.someDiffNum);
this.someService.applyCalc(someNum, this.someDiffNum);
}
I'm using the OnPush change detection strategy, but nothing changed. How can the sibling component A run with the data changes from component B?
I'm not sure why you're using ViewChild there but if it is to update the child components manually when there's change then that should be a red flag something is being done wrong, if you have data that needs to be shared it should be shared across the board and update accordingly on the single source of data changes without having to manually update the rest of the places.
Now to your problem:
If you're using the OnPush change detection strategy you have to update your data in an immutable way or use Observables, otherwise the change detection won't trigger.
Some people will advice triggering change detection manually but I'd recommend avoiding that as the whole point of using OnPush is to avoid a whole page render unnecessarily.
A simple solution I like to use is to use a Subject or BehaviorSubject instead with the async pipe. This way it ensures smooth work with the OnPush change detection strategy because ChangeDetection will run when the Observable emits a new value, and the async pipe takes care of unsubscribing the Observable for you.
If I were to modify your current components, it'd look something like this:
Parent:
num$ = new Subject<number>();
otherNum$ = new Subject<number>();
setSomeNum(someNum: number) {
this.num$.next(someNum);
}
setSomeOtherNum (someOtherNum: number) {
// this is run on someDiffNumEmitter in Component B
this.otherNum$.next(someOtherNum)
}
Then in the HTML you can use the async pipe, like this:
<some-component [num]="num$ | async" [otherNum]="otherNum$ | async"></some-component>
(You could use the async pipe in the component itself, doesn't really matter).
And that's pretty much it. You should have a Subject as an Observable, then share it with child components, once the Observable is updated, the child components data will be updated as well.
One small caveat is that when using a Subject instead of a BehaviorSubject is to make sure to subscribe before emitting any values to the Subject, otherwise the data will not update. So for certain cases BehaviorSubject is a better fit.
parent.component.html
<parent-comp [data]="mydata"> </parent-comp>
parent.component.ts
this.service.abc$
.takeUntil(this.ngUnsubscribe.asObservable())
.subscribe((data: myType[]) => {
this.mydata= data;
});
child.component.ts
#Input data;
Under Class I have below code
public ngOnChanges() { if (this.data) { console.log(this.data); } }
Now I want whenever I receive latest data in #Input data from Parent Component to child then my ngOnChanges function should trigger and print data in console.
But unfortunately ngOnChanges function does not trigger again. It trigger only once when component initialize
Please let me know if anyone wants more detail on same!
Thanks!
Given the lack of further information, I'd make an informed guess that #Input data is either an array or an object.
According to docs, ngOnChanges is:
A lifecycle hook that is called when any data-bound property of a
directive changes.
What it doesn't say however is how the property should be changed. Generally speaking, the hook is only triggered when the reference to the property is changed.
Consider the following eg.
Parent component controller
mydata = [];
updateMyData(value: any) {
this.mydata.push(value);
}
Parent component template
<app-child [data]="mydata"></app-child>
Child component controller
#Input() data: any;
ngOnChanges(changes: SimpleChanges) {
console.log(changes);
}
Now you'd expect the ngOnChanges will be triggered every time the updateMyData() function is called in the parent component. But the reference to the variable mydata is never changed. So the hook won't be triggered. There are multiple ways to force the change detector to trigger the hook.
Method 1:
Bind the #Input decorator to a setter instead of the variable directly. Discussed in an answer already.
Method 2:
Use spread syntax to re-assign the variable in the parent component.
Parent component controller
mydata = [];
updateMyData(value: any) {
this.mydata = [...this.mydata, value];
}
You could use the same methods for objects as well.
Thank you everyone for your quick and effective solutions.
I got solution and it is not exactly but similar to what you guys suggested.
In the Parent Component:
**Earlier I was assigning this way**
`this.mydata= data;`
**But now I am assigning in below way:**
`this.mydata= cloneDeep(data);`
Note : cloneDeep is imported from lodash
It could be the data you are passing down. If it doesn't change then the ngOnChanges won't register any changes. Here's an example, you can see if a property changes multiple times then it will only trigger on the first update, but if you recreate the object it changes every time.
(see console logs in stackblitz)
https://stackblitz.com/edit/angular-ivy-qmb35h?file=src%2Fapp%2Fapp.component.html
You can do as I did and recreate the object each time to bypass this, or a more hacky way may be to keep a dummy 'count' variable that you pass down as well, and increment it each time you want the child component to register the change.
NgOnChanges will only be triggered for an input-bound property change of primitive type. That is because the reference to the data-variable has to be changed in order for the change to be registered, so that you can get it in this life-cycle hook.
So, the possible way you could achieve this is by changing the reference of 'mydata' variable. Like, assigning a new reference to the mydata variable when it is changed, mydata = [...mydata] if it is an array, or mydata = {...mydata} if it is an object from the parent component.
you can use setter and getter methods for #Input in angular.
Please refer the below lines for reference:
private _data: any;
#Input()
set data(data) {
this._data = data;
console.log(this._data);
};
From setter method only you can call any other method as well and can run any logic you want from there.
I am trying to implement two way data binding in angular components. Currently its in a parent child mode.
parent.component.html
<child [(title)]="title"></child>
<span style="color: red">This is parent component {{title}}</span>
parent.component.ts
title = 'app';
child.component.html
<span style="color: blue">This is child component {{title}}</span>
child.component.ts
#Input() title: any;
#Output() pushTitle = new EventEmitter();
constructor() { }
ngOnInit() {
this.title = 'new title';
this.pushTitle.emit(this.title);
}
The title should implement on the parent component as well, when I change it from the child. Also, I am not sure why the parent code keeps going in a loop for no reason. I have added text in html just to test if its updated in both the components, but its updating only in the child, and not in the parent. I am coming from the angularjs background, and two way data binding worked seamlessly in it. I am just confused what I am doing wrong(I know its a noob question).
Demo here: https://stackblitz.com/edit/angular-xttmxg
There is another way that you can achive the same.
#Input() title: any;
#Output() titleChange: EventEmitter<any> = new EventEmitter<any>();
changeValue() {
this.title= !title;
this.titleChange.emit(this.title);
}
Have a look at Angular documentation about two way binding
Two way data binding only works for template - component interaction.
If you want to send title change to parent component, you should do something like this:
Parent template and component:
<child [title]="title" (pushTitle)="onTitleChange(value)"></child>
<span style="color: red">This is parent component {{title}}</span>
onTitleChange(value) {
this.title = value;
}
Followup question:
Template:
<input [(ngModel)]="inputModel">
Component:
inputModel: string;
Now, every time you type something into input field you will see changes in component model, OR when change inputModel value programmatically, you will see the change in HTML input.
You are somehow creating an infinite update cycle using the 2-way-binding. This leads to the infinite loop and eventual stack overflow you noticed.
To fix this, preferably, you want to add some logic to the titleChange event (this is the banana-part of the banana-in-a-box syntax, i.e. the part in parens in [(title)] which is getting automatically translated into an event emitter named titleChange).
For example, you might want to skip updating the title property of the parent component if its equal to the update emitted by the child component.
This means you should split up [(title)] into (titleChange)="titleChange($event)" and [title]="title". The first part lets you pass the updated title as $event and then process it in a function titleChanged (name is arbitrary in this case). The second part has the effect that the child component receives updates of the parent component's title property.
Another common pattern is to make title private (commonly with a prefixed underscore, e.g. _title) and then add a getter get title() { return this._title;} so that you can (1) encapsulate this property and (2) add some processing.
In your case this is not needed, but it doesn't hurt either. ;-)
Here's a plunkr containing these changes.
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!