ExpressionChangedAfterItHasBeenCheckedError Explai

2019-01-02 20:16发布

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.

16条回答
不流泪的眼
2楼-- · 2019-01-02 20:26

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 the ngOnInit() life cycle hook of another component.

According to the documentation, this method is called after Angular has already detected changes:

Called once, after the first ngOnChanges().

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:

constructor(private globalEventsService: GlobalEventsService) {

}

ngOnInit() {
    this.globalEventsService.showCheckoutHeader = true;
}

To this:

constructor(private globalEventsService: GlobalEventsService) {
    this.globalEventsService.showCheckoutHeader = true;
}

ngOnInit() {

}

and it fixed the problem :)

查看更多
浅入江南
3楼-- · 2019-01-02 20:28

Follow the below steps:

1. Use 'ChangeDetectorRef' by importing it from @angular/core as follows:

import{ ChangeDetectorRef } from '@angular/core';

2. Implement it in constructor() as follows:

constructor(   private cdRef : ChangeDetectorRef  ) {}

3. Add the following method to your function which you are calling on an event like click of button. So it look like this:

functionName() {   
    yourCode;  
    //add this line to get rid of the error  
    this.cdRef.detectChanges();     
}
查看更多
刘海飞了
4楼-- · 2019-01-02 20:29

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.

 setTimeout(() => {
        this.isLoading = true;
    });

3) If you really want to execute your code on same VM use like

Promise.resolve(null).then(() => this.isLoading = true);

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.

查看更多
栀子花@的思念
5楼-- · 2019-01-02 20:31

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, add constructor(private changeDetector : ChangeDetectorRef ) {} and then

ngAfterViewChecked(){
  this.changeDetector.detectChanges();
}

I hope this helps other as many others have helped me.

查看更多
浮光初槿花落
6楼-- · 2019-01-02 20:32

In my case, I had this problem in my spec file, while running my tests.

I had to change ngIf to [hidden]

<app-loading *ngIf="isLoading"></app-loading>

to

<app-loading [hidden]="!isLoading"></app-loading>
查看更多
余欢
7楼-- · 2019-01-02 20:32

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.

*ngIf="isProcessing()" 

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.

[hidden]="isProcessing()"

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].

查看更多
登录 后发表回答