请向我解释为什么我一直得到这个错误:ExpressionChangedAfterItHasBeenCheckedError:表达式已经改变后,它被检查。
显然,我只有在开发模式下才会遇到这种情况,在我的产品构建中不会出现这种情况,但这非常烦人,而且我根本不明白在我的开发环境中出现错误而不会在prod上显示的好处——可能是因为我缺乏理解。
通常,修复很简单,我只是把导致错误的代码包装在setTimeout中,就像这样:
setTimeout(()=> {
this.isLoading = true;
}, 0);
或者使用如下构造函数强制检测更改:
this.isLoading = true;
this.cd.detectChanges();
但是为什么我总是遇到这个错误呢?我想要了解它,这样我就可以在将来避免这些俗套的修复。
当值在同一个更改检测周期中更改多次时,将发生此错误。我在一个TypeScript getter中遇到了这个问题,它的返回值经常变化。要解决这个问题,您可以限制一个值,使其在每个更改检测周期中只能更改一次,如下所示:
import { v4 as uuid } from 'uuid'
private changeDetectionUuid: string
private prevChangeDetectionUuid: string
private value: Date
get frequentlyChangingValue(): any {
if (this.changeDetectionUuid !== this.prevChangeDetectionUuid) {
this.prevChangeDetectionUuid = this.changeDetectionUuid
this.value = new Date()
}
return this.value
}
ngAfterContentChecked() {
this.changeDetectionUuid = uuid()
}
HTML:
<div>{{ frequentlyChangingValue }}</div>
这里的基本方法是每个变更检测周期都有自己的uuid。当uuid改变时,您就知道您已经进入了下一个循环。如果循环已经改变,则更新值并返回它,否则只需返回与之前在此循环中返回的值相同的值。
这确保每个循环只返回一个值。这对于频繁更新值很有效,因为更改检测周期发生得非常频繁。
为了生成uuid,我使用了uuid npm模块,但你可以使用任何方法来生成唯一的随机uuid。
@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。突然你得到这个错误,它似乎没有任何意义。
请遵循以下步骤:
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();
}