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

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

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

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

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

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

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


当前回答

尝试了上面建议的大部分解决方案。只有在这种情况下对我有用。我使用*ngIf来切换角材料的不确定渐进条基于api调用,它抛出ExpressionChangedAfterItHasBeenCheckedError。

在上述组件中:

constructor(
    private ngZone: NgZone,
    private changeDetectorRef: ChangeDetectorRef,
) {}

ngOnInit() {
    this.ngZone.runOutsideAngular(() => {
        this.appService.appLoader$.subscribe(value => {
            this.loading = value;
            this.changeDetectorRef.detectChanges();
        });
    });
}

诀窍是使用ngzone绕过角分量的变化检测。

PS:不确定这是否是一个优雅的解决方案,但是使用AfterContentChecked和AfterViewChecked生命周期钩子必然会引起性能问题,因为你的应用程序会越来越大,因为它会被多次触发。

其他回答

在@ViewChild装饰器中添加static: true

@ViewChild(UploadComponent, { static: true }) uploadViewChild: UploadComponent;

一旦我理解了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() {

}

它解决了这个问题:)

此错误表明应用程序中存在真正的问题,因此抛出异常是有意义的。

在devMode中,更改检测在每次常规的更改检测运行之后增加一个额外的回合,以检查模型是否已更改。

如果模型在常规变化检测轮和附加变化检测轮之间发生了变化,则这表明也发生了变化

变更检测本身导致了变更 每次调用方法或getter都会返回不同的值

这两个都不好,因为不清楚如何继续,因为模型可能永远不会稳定。

如果Angular一直运行变更检测直到模型稳定,那么它可能会一直运行下去。 如果Angular不运行变更检测,那么视图可能不会反映模型的当前状态。

参见Angular2中生产模式和开发模式的区别是什么?

@HostBinding可能是这个错误的一个令人困惑的来源。

例如,假设您在组件中有以下主机绑定

// image-carousel.component.ts
@HostBinding('style.background') 
style_groupBG: string;

为了简单起见,我们假设这个属性是通过以下输入属性更新的:

@Input('carouselConfig')
public set carouselConfig(carouselConfig: string) 
{
    this.style_groupBG = carouselConfig.bgColor;   
}

在父组件中,你通过编程方式在ngAfterViewInit中设置它

@ViewChild(ImageCarousel) carousel: ImageCarousel;

ngAfterViewInit()
{
    this.carousel.carouselConfig = { bgColor: 'red' };
}

事情是这样的:

Your parent component is created The ImageCarousel component is created, and assigned to carousel (via ViewChild) We can't access carousel until ngAfterViewInit() (it will be null) We assign the configuration, which sets style_groupBG = 'red' This in turn sets background: red on the host ImageCarousel component This component is 'owned' by your parent component, so when it checks for changes it finds a change on carousel.style.background and isn't clever enough to know that this isn't a problem so it throws the exception.

一种解决方案是在ImageCarousel内部引入另一个包装器div,并在其上设置背景颜色,但这样就无法获得使用HostBinding的一些好处(例如允许父类控制对象的完整边界)。

在父组件中,更好的解决方案是在设置配置之后添加detectChanges()。

ngAfterViewInit()
{
    this.carousel.carouselConfig = { ... };
    this.cdr.detectChanges();
}

这看起来很明显,和其他答案很相似,但有细微的区别。

考虑这样一种情况,您直到开发的后期才添加@HostBinding。突然你得到这个错误,它似乎没有任何意义。

Angular会运行更改检测,当它发现传递给子组件的某些值被更改时,Angular会抛出以下错误:

点击查看更多信息

为了纠正这个问题,我们可以使用AfterContentChecked生命周期钩子和

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

  constructor(
  private cdref: ChangeDetectorRef) { }

  ngAfterContentChecked() {

    this.cdref.detectChanges();

  }