请向我解释为什么我一直得到这个错误:ExpressionChangedAfterItHasBeenCheckedError:表达式已经改变后,它被检查。

显然,我只有在开发模式下才会遇到这种情况,在我的产品构建中不会出现这种情况,但这非常烦人,而且我根本不明白在我的开发环境中出现错误而不会在prod上显示的好处——可能是因为我缺乏理解。

通常,修复很简单,我只是把导致错误的代码包装在setTimeout中,就像这样:

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

或者使用如下构造函数强制检测更改:

this.isLoading = true;
this.cd.detectChanges();

但是为什么我总是遇到这个错误呢?我想要了解它,这样我就可以在将来避免这些俗套的修复。


当前回答

请遵循以下步骤:

1. 通过从@angular/core导入'ChangeDetectorRef'来使用它,如下所示:

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

2. 在constructor()中实现,如下所示:

constructor(   private cdRef : ChangeDetectorRef  ) {}

3. 添加下面的方法到你的函数,你正在调用的事件,如点击按钮。它看起来是这样的:

functionName() {   
    yourCode;  
    //add this line to get rid of the error  
    this.cdRef.detectChanges();     
}

其他回答

一旦我理解了Angular生命周期钩子以及它们与变更检测的关系,我就理解了很多。

我试图让Angular更新一个绑定到元素的*ngIf的全局标志,我试图在另一个组件的ngOnInit()生命周期钩子中更改这个标志。

根据文档,这个方法会在Angular检测到变化之后调用:

在第一个ngOnChanges()之后调用一次。

因此,在ngOnChanges()中更新标志不会启动变更检测。然后,一旦变更检测再次自然触发,标志的值就发生了变化,并抛出错误。

在我的例子中,我修改了这个:

constructor(private globalEventsService: GlobalEventsService) {

}

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

:

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

ngOnInit() {

}

它解决了这个问题:)

我面临着同样的问题,因为我的组件中的一个数组的值发生了变化。但是我没有检测值变化的变化,而是将组件变化检测策略改为onPush(它将检测对象变化的变化,而不是值变化的变化)。

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush
    selector: -
    ......
})

参考文章https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

因此,变更检测背后的机制实际上是以同步执行变更检测和验证摘要的方式工作的。这意味着,如果我们异步更新属性,当验证循环运行时,值不会被更新,我们也不会得到ExpressionChanged…错误。我们得到这个错误的原因是,在验证过程中,Angular看到的值与它在变更检测阶段记录的值不同。所以为了避免....

1)使用changeDetectorRef

2)使用setTimeOut。这将在另一个VM中作为宏任务执行您的代码。Angular在验证过程中不会看到这些更改,你也不会得到这个错误。

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

3)如果你真的想在相同的虚拟机上执行你的代码使用

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

这将创建一个微任务。微任务队列是在当前同步代码完成执行之后处理的,因此对属性的更新将在验证步骤之后发生。

在我的情况下,我在LoadingService中有一个行为主题是loading的异步属性

使用[hidden]模型可以工作,但是*ngIf失败

    <h1 [hidden]="!(loaderService.isLoading | async)">
        THIS WORKS FINE
        (Loading Data)
    </h1>

    <h1 *ngIf="!(loaderService.isLoading | async)">
        THIS THROWS ERROR
        (Loading Data)
    </h1>

我在Ionic3(它使用Angular 4作为其技术堆栈的一部分)中遇到过这种错误。

对我来说,它是这样的:

< ion-icon[名字]= " getFavIconName ()" > < / ion-icon >

所以我试着有条件地改变离子图标的类型,从大头针到删除圈,根据屏幕运行的模式。

我猜我将不得不添加一个*ngIf代替。