I have a simple alert component which I'm creating dynamically in the view. Since it's created dynamically I've set an option to automatically display the alert after it has been initialised.
Although it's working I'd like to understand why I have to manually trigger the change detection in this particular case.
Code:
export class OverlayMessageComponent implements AfterViewInit {
...
ngAfterViewInit() {
if(this.autoShow) {
this.show();
}
this.changeDetector.detectChanges();
}
...
}
Full Sample: https://plnkr.co/edit/8NvfhDvLVBd71I7DR0kW
I had to add this.changeDetector.detectChanges();
as I was getting the following error:
EXCEPTION: Expression has changed after it was checked.
I was under the impression that using AfterViewInit
helps avoiding the issue, but I think I'm assuming wrong. Is there a way to structure the code better to avoid this error?
I'd like to understand better why this error is returned. I've seen this error a few times before, and I know that some say that with a setTimeout()
or enableProdMode()
does solve the issue, but to me it seems a hacky workaround when the framework itself is notifying you that there's a problem.
Use
detectChanges()
when you've updated the model after angular has run it's change detection, or if the update hasn't been in angular world at all.The
AfterViewInit
andAfterViewChecked
lifecycle hooks are triggered after change detection has completed and the view has been built. So any code that runs at this point should not update the view, or your app and its view will fall out of sync. Take a look at the docsAs a result, you must either manually trigger change detection -- which is an expensive operation because Angular has to go through the whole App again -- or make the change asynchronously so that the view will be updated at the next change detection step, with something such as:
Or more simply, if you don't need to grab a handle of something in the view, you can run your code in
ngOnInit()
or a little later inngAfterContentInit()
. Because these run before the view is composed, you can make changes that affect the view without trouble.Docs: lifecycle hooks order
The fix
For your particular case there's no need to trigger change detection or use async update. The fix is simple, just move the
this.show
to thengOnInit
lifecycle hook:The explanatation
Because you're using
bringIconToFront
component property in the template binding:Angular should update the DOM of the
App
component. Also, Angular calls lifecycle hooks for the childOverlayMessage
component. DOM udpate and lifecycle hooks are performed in order as shown here:OnInit
andngDoCheck
on a child component (OnInit
is called only during first check)App
view if properties on current view component instance changed`ngAfterViewInit
andngAfterViewChecked
for the childOverlayMessage
componentngAfterViewInit
andngAfterViewChecked
for the currentApp
componentYou can see that the
onInit
is called before the DOM bindings are updated for the current component. And thengAfterViewInit
is called after. That is why it works in one case and doesn't work in the other.This article will help you understand the error better - Everything you need to know about the
ExpressionChangedAfterItHasBeenCheckedError
error.