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

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

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

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

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

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

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


当前回答

当我添加*ngIf时,我的问题很明显,但这不是原因。该错误是由于在{{}}标签中更改模型,然后稍后尝试在*ngIf语句中显示更改后的模型而导致的。这里有一个例子:

<div>{{changeMyModelValue()}}</div> <!--don't do this!  or you could get error: ExpressionChangedAfterItHasBeenCheckedError-->
....
<div *ngIf="true">{{myModel.value}}</div>

为了解决这个问题,我将调用changeMyModelValue()的位置更改为更有意义的位置。

在我的情况下,我希望每当子组件更改数据时调用changeMyModelValue()。这要求我在子组件中创建并发出一个事件,以便父组件可以处理它(通过调用changeMyModelValue())。看到https://angular.io/guide/component-interaction parent-listens-for-child-event

其他回答

有一些有趣的答案,但我似乎没有找到一个符合我的需求,最接近的是来自@chittrang-mishra,它只指一个特定的功能,而不是像我的应用程序中那样有几个切换。

我不想使用[隐藏]利用*ngIf甚至不是DOM的一部分,所以我找到了以下解决方案,这可能不是最好的,因为它抑制错误而不是纠正它,但在我的情况下,我知道最终结果是正确的,这似乎对我的应用程序是可以的。

我所做的是实现AfterViewChecked,添加构造函数(私有changeDetector: ChangeDetectorRef){},然后

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

我希望这能帮助到其他人,就像其他人帮助过我一样。

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

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

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

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/

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

对我来说,它是这样的:

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

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

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

@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。突然你得到这个错误,它似乎没有任何意义。