Issue
I've read about NG0100: ExpressionChangedAfterItHasBeenCheckedError in Angular, but I can't avoid it in my case.
Basically, on the interceptor, I've a service that load a "status" true/false:
intercept(req: HttpRequest, next: HttpHandler): Observable { this.showLoader();
return next.handle(req).pipe(
catchError((error) => {
return error;
}),
finalize(() => {
this.hideLoader();
})
);
}
Using ngAfterViewInit
within app component introduce that error:
ngOnInit(): void {}
ngAfterViewInit() {
this.getData().subscribe((data) => {
this.childSelector.loadRecipeRoadmap(data.name);
});
}
And I need to use it: in fact, when all child are loaded, parent must "send" (once) data to child (only at the beginning). And at some point, I just need to read from child (that's why I use ViewChild and not @Output mechanism).
How can I fix this specific error? Should I sync Observable? Not sure how...
Solution
It's because you are executing some code in the ngAfterViewInit
, which modifies the data that is displayed.
First detection cycle evaluated the ngIf
to false
, then ngAfterViewInit
was executed, then after it the second verifying detection cycle (angular development mode has this additional one) was executed and this time ngIf
was evaluated to true
. Hence the infamous error.
There are a few solutions to this:
option 1: next macrotask (setTimeout)
Since your data fetching is asynchronous anyway, you can postpone it to be called in next macrotask (after ngAfterViewInit
is finished) with a help of setTimeout
with 0 time delay:
ngAfterViewInit() {
setTimeout(() => {
this.getData().subscribe((data) => {
this.childSelector.loadRecipeRoadmap(data.name);
});
});
}
option 2: ChangeDetectorRef.detectChanges()
You may inform angular to run additional detection cycle after you modified the data that is displayed. Although, from performance reasons, this is worse option that the previous one.
constructor(private http: HttpClient, public loader: LoaderService, private changeDetectorRef: ChangeDetectorRef) {}
ngAfterViewInit() {
this.getData().subscribe((data) => {
this.childSelector.loadRecipeRoadmap(data.name);
});
this.changeDetectorRef.detectChanges();
}
Or maybe more angular-ish way:
option 3: fetching data in OnInit + data binding
Instead of waiting for child to be created and interact with it directly, execute data loading in OnInit
and bind the data to the child component:
export class AppComponent {
dataForChild: any;
constructor(private http: HttpClient, public loader: LoaderService) {}
ngOnInit(): void {
this.getData().subscribe((data) => this.dataForChild = data.name);
}
And bind it with (or course myData
in child component need to become and input property with @Input()
)
<app-my-child [myData]="dataForChild"></app-my-child>
Extra - pending subscription
You are subscribing to the observable, but do not unsubscribe from it (neither with unsubribe()
nor using takeUntil()
). Although subscriptions to observables from angular http module doesn't cause memory leaks, it's good to make sure you will unsubscribe from it anyway for a few reasons:
- just because it's a good practice, without wondering whether it's necessary or not. Today it may not be necessary, but tomorrow someone will modify the service and it may not be safe observable anymore;
- If you unsubscibe in
ngOnDestroy
, the pending http request will be canceled, freeing browser pool of used requests - also no logic in the subscription and in the potential
pipe
'd transformations won't be called, so better performance due to not executing unnecessary code - and it will allow garbage collector to remove component instance faster
Answered By - carecki
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.