Updating the boolean in AfterViewInit causes the "Expression" to change after it was checked. "
I have a simple alert component that I create dynamically in a view. Since it was created dynamically, I set it up to be able to automatically display a warning after it was initialized.
While it works, I would like to understand why I have to manually trigger 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: The expression was changed after it was tested.
I was under the impression that using it AfterViewInit
helps avoid the problem, but I think I am wrong. Is there a better way to structure your code to avoid this error?
I would like to better understand why this error is being returned. I've seen this error a few times before, and I know some say that with setTimeout()
or enableProdMode()
actually solves the problem, but to me this seems like a hacky workaround where the framework itself notifies you that there is a problem.
source to share
Correction
In your particular case, there is no need to run change detection or use asynchronous update. The fix is simple, just move this.show
to the ngOnInit
lifecycle hook:
ngOnInit() {
if(this.autoShow) {
this.show();
}
}
Explanation
Since you are using the component property bringIconToFront
in the template binding:
<div class="icon home" [class.add-z-index]="bringIconToFront"></div>
Angular needs to update the DOM of the component App
. Additionally, Angular calls lifecycle bindings for the child component OverlayMessage
. The DOM jumpers udpate and lifecycle are done as shown here :
- causes
OnInit
andngDoCheck
subsidiary component (OnInit
called only during the first scan) - updates interpolations and DOM bindings for the current view
App
if the properties of a component instance of the current view are changed - calls
ngAfterViewInit
andngAfterViewChecked
for the child componentOverlayMessage
- calls
ngAfterViewInit
alsongAfterViewChecked
for the current componentApp
You can see that the call OnInit
is being called before the DOM bindings are updated for the current component. And it ngAfterViewInit
is called after. This is why it works in one case and doesn't work in another.
This article will help you understand the error better -
Everything you need to know about the errorExpressionChangedAfterItHasBeenCheckedError
.
source to share
I would like to better understand why this error is being returned
The hooks are lifecycle AfterViewInit
and AfterViewChecked
run after change detection and view creation are complete. Thus, any code that is executed in this step should not update the view, or your application and its view will fall out of sync. Take a look at the docs
Angular The unidirectional data flow rule prevents the view from being updated after it has been created. Both of these hooks light up after composing the component view.
Angular throws an error if the hook immediately updates the component's data-bound comment property.
As a result, you must either manually initiate change detection, which is an expensive operation because Angular has to go through the entire application again - or make the change asynchronous so that the view is updated in the next change detection step, with something like:
if(this.autoShow) { setTimeout(()=>this.show,0)}
Or, more simply, if you don't need to grab a handle to something in the view, you can run your code in ngOnInit()
or a little later in ngAfterContentInit()
. Because they are executed before the view is drawn up, you can make changes that affect the view without issue.
source to share