Please explain to me why I keep getting this error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
Obviously, I only get it in dev mode, it doesn't happen on my production build, but it's very annoying and I simply don't understand the benefits of having an error in my dev environment that won't show up on prod --probably because of my lack of understanding.
Usually, the fix is easy enough, I just wrap the error causing code in a setTimeout like this:
setTimeout(()=> {
this.isLoading = true;
}, 0);
Or force detect changes with a constructor like this: constructor(private cd: ChangeDetectorRef) {}
:
this.isLoading = true;
this.cd.detectChanges();
But why do I constantly run into this error? I want to understand it so I can avoid these hacky fixes in the future.
A lot of understanding came once I understood the Angular Lifecycle Hooks and their relationship with change detection.
I was trying to get Angular to update a global flag bound to the
*ngIf
of an element, and I was trying to change that flag inside of thengOnInit()
life cycle hook of another component.According to the documentation, this method is called after Angular has already detected changes:
So updating the flag inside of
ngOnChanges()
won't initiate change detection. Then, once change detection has naturally triggered again, the flag's value has changed and the error is thrown.In my case, I changed this:
To this:
and it fixed the problem :)
Follow the below steps:
1. Use 'ChangeDetectorRef' by importing it from @angular/core as follows:
2. Implement it in constructor() as follows:
3. Add the following method to your function which you are calling on an event like click of button. So it look like this:
Referring to the article https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4
So the mechanics behind change detection actually works in a way that both change detection and verification digests are performed synchronously. That means, if we update properties asynchronously the values will not be updated when the verification loop is running and we will not get
ExpressionChanged...
error. The reason we get this error is, during the verification process, Angular sees different values then what it recorded during change detection phase. So to avoid that....1) Use changeDetectorRef
2) use setTimeOut. This will execute your code in another VM as a macro-task. Angular will not see these changes during verification process and you will not get that error.
3) If you really want to execute your code on same VM use like
This will create a micro-task. The micro-task queue is processed after the current synchronous code has finished executing hence the update to the property will happen after the verification step.
There were interesting answers but I didn't seem to find one to match my needs, the closest being from @chittrang-mishra which refers only to one specific function and not several toggles as in my app.
I did not want to use
[hidden]
to take advantage of*ngIf
not even being a part of the DOM so I found the following solution which may not be the best for all as it suppresses the error instead of correcting it, but in my case where I know the final result is correct, it seems ok for my app.What I did was implement
AfterViewChecked
, addconstructor(private changeDetector : ChangeDetectorRef ) {}
and thenI hope this helps other as many others have helped me.
In my case, I had this problem in my spec file, while running my tests.
I had to change
ngIf
to[hidden]
to
Here's my thoughts on what is happening. I have not read the documentation but am sure this is part of why the error is shown.
When using *ngIf, it physically changes the DOM by adding or removing the element every time the condition changes. So if the condition changes before it is rendered to the view (which is highly possible in Angular's world), the error is thrown. See explanation here between development and production modes.
When using [hidden] it does not physically change the DOM but merely hiding the element from the view, most likely using CSS in the back. The element is still there in the DOM but not visible depending on the condition's value. That is why the error will not occur when using [hidden].