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

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

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

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

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

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

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


当前回答

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

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

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

其他回答

我也遇到过类似的问题。查看生命周期钩子文档,我将ngAfterViewInit更改为ngAfterContentInit,它工作正常。

更新

我强烈建议首先从OP的自我响应开始:适当地考虑在构造函数中可以做什么,而在ngOnChanges()中应该做什么。

原始

这与其说是一个答案,不如说是一个题外话,但它可能会帮助到一些人。当我试图让按钮的存在取决于表单的状态时,我偶然发现了这个问题:

<button *ngIf="form.pristine">Yo</button>

据我所知,这种语法会导致根据条件从DOM中添加和删除按钮。这又会导致ExpressionChangedAfterItHasBeenCheckedError。

在我的案例中,解决方案(尽管我没有声称掌握了差异的全部含义)是使用display: none来代替:

<button [style.display]="form.pristine ? 'inline' : 'none'">Yo</button>

setTimeout或delay(0)如何解决这个问题?

以下是上面的代码修复问题的原因:

The initial value of the flag is false, and so the loading indicator will NOT be displayed initially

ngAfterViewInit() gets called, but the data source is not immediately called, so no modifications of the loading indicator will be made synchronously via ngAfterViewInit()

Angular then finishes rendering the view and reflects the latest data changes on the screen, and the Javascript VM turn completes

One moment later, the setTimeout() call (also used inside delay(0)) is triggered, and only then the data source loads its data

the loading flag is set to true, and the loading indicator will now be displayed

Angular finishes rendering the view, and reflects the latest changes on the screen, which causes the loading indicator to get displayed

这一次没有发生错误,因此这修复了错误消息。

来源:https://blog.angular-university.io/angular-debugging/

调试技巧

这个错误可能非常令人困惑,而且很容易对它发生的确切时间做出错误的假设。我发现在受影响的组件的适当位置添加大量这样的调试语句很有帮助。这有助于理解流程。

在父类的put语句中(确切的字符串'EXPRESSIONCHANGED'很重要),但除此之外,这些只是例子:

    console.log('EXPRESSIONCHANGED - HomePageComponent: constructor');
    console.log('EXPRESSIONCHANGED - HomePageComponent: setting config', newConfig);
    console.log('EXPRESSIONCHANGED - HomePageComponent: setting config ok');
    console.log('EXPRESSIONCHANGED - HomePageComponent: running detectchanges');

在子/ services / timer回调函数中:

    console.log('EXPRESSIONCHANGED - ChildComponent: setting config');
    console.log('EXPRESSIONCHANGED - ChildComponent: setting config ok');

如果你手动运行detectChanges,添加日志记录:

    console.log('EXPRESSIONCHANGED - ChildComponent: running detectchanges');
    this.cdr.detectChanges();

然后在Chrome调试器只是过滤'EXPRESSIONCHANGES'。这将显示所有设置的流程和顺序,以及Angular抛出错误的确切位置。

您还可以单击灰色链接来放入断点。

另一件需要注意的事情是,如果你在整个应用程序中都有类似命名的属性(比如style.background),请确保你正在调试你认为你在调试的属性——通过将它设置为一个模糊的颜色值。

对于我的问题,我正在阅读github -“在afterViewInit中更改组件“非模型”值时的ExpressionChangedAfterItHasBeenCheckedError”,并决定添加ngModel

<input type="hidden" ngModel #clientName />

它解决了我的问题,我希望它能帮助到别人。