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.
Navigate up the call stack until you find a template expression where the value displayed in the error has changed. Ensure that there are no changes to the bindings in the template after change detection is run. This often means refactoring to use the correct component lifecycle hook for your use case.
ngAfterContentInit : This is called after components external content has been initialized. ngAfterViewInit : This is called after the component view and its child views has been initialized.
ngAfterViewInit()linkA callback method that is invoked immediately after Angular has completed initialization of a component's view. It is invoked only once when the view is instantiated.
What is Change Detection Strategy in Angular ? Angular Change Detection Strategy are the methods by which the updates to the component is tracked and component is triggered to Re-render.
I'd like to understand better why this error is returned
The AfterViewInit
and AfterViewChecked
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 docs
Angular's unidirectional data flow rule forbids updates to the view after it has been composed. Both of these hooks fire after the component's view has been composed.
Angular throws an error if the hook updates the component's data-bound comment property immediately.
As 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:
if(this.autoShow) { setTimeout(()=>this.show,0)}
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 in ngAfterContentInit()
. Because these run before the view is composed, you can make changes that affect the view without trouble.
Docs: lifecycle hooks order
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 the ngOnInit
lifecycle hook:
ngOnInit() {
if(this.autoShow) {
this.show();
}
}
Because you're using bringIconToFront
component property in the template binding:
<div class="icon home" [class.add-z-index]="bringIconToFront"></div>
Angular should update the DOM of the App
component. Also, Angular calls lifecycle hooks for the child OverlayMessage
component. DOM udpate and lifecycle hooks are performed in order as shown here:
OnInit
and ngDoCheck
on a child component (OnInit
is called only during first check)App
view if properties on current view component instance changed`ngAfterViewInit
and ngAfterViewChecked
for the child OverlayMessage
componentngAfterViewInit
and ngAfterViewChecked
for the current App
componentYou can see that the onInit
is called before the DOM bindings are updated for the current component. And the ngAfterViewInit
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With