请向我解释为什么我一直得到这个错误:ExpressionChangedAfterItHasBeenCheckedError:表达式已经改变后,它被检查。
显然,我只有在开发模式下才会遇到这种情况,在我的产品构建中不会出现这种情况,但这非常烦人,而且我根本不明白在我的开发环境中出现错误而不会在prod上显示的好处——可能是因为我缺乏理解。
通常,修复很简单,我只是把导致错误的代码包装在setTimeout中,就像这样:
setTimeout(()=> {
this.isLoading = true;
}, 0);
或者使用如下构造函数强制检测更改:
this.isLoading = true;
this.cd.detectChanges();
但是为什么我总是遇到这个错误呢?我想要了解它,这样我就可以在将来避免这些俗套的修复。
@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。突然你得到这个错误,它似乎没有任何意义。
此错误表明应用程序中存在真正的问题,因此抛出异常是有意义的。
在devMode中,更改检测在每次常规的更改检测运行之后增加一个额外的回合,以检查模型是否已更改。
如果模型在常规变化检测轮和附加变化检测轮之间发生了变化,则这表明也发生了变化
变更检测本身导致了变更
每次调用方法或getter都会返回不同的值
这两个都不好,因为不清楚如何继续,因为模型可能永远不会稳定。
如果Angular一直运行变更检测直到模型稳定,那么它可能会一直运行下去。
如果Angular不运行变更检测,那么视图可能不会反映模型的当前状态。
参见Angular2中生产模式和开发模式的区别是什么?
当我添加*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