不知各位冰雪聪明的朋友们是否遇到过如下错误?
1 | NG0100: Expression has changed after it was checked |
大家在第一次遇到这个错误的时候是什么反应呢?反正我是一头雾水,今天我就带大家揭开这个错误的神秘面纱,我现在很困。。。非常不想写,但是我还是要写,因为我是一个有责任心的人。
1 | 为什么我的眼里常含泪水?因为我太困了。。。 |
言归正传!其实从这个错误的描述来看,聪明的你已经发现了,那就是在Angular刚刚进行完一次变更检测,你又更新了某个值。那么,有哪些场景会导致这个错误呢?我们依次来看。
先来看一个最简单的例子:
Update value in ngAfterViewInit
1 | <!-- app.component.html --> |
1 | // app.component.ts |
这个例子中,我们在ngAfterViewInit
生命周期钩子中更新了count
的值,在ngAfterViewInit
之前,Angular刚刚进行完一次变更检测,我们在ngAfterViewInit
中更新了count
的值,此时下一次变更检测还未开始,所以就产生了这个错误。
他来了,他来了,他迈着箭步走来了!
我们可以点击红框中的文件链接,查看到底哪个变量出了问题。
既然错误已经发生了,那么该如何解决它呢?
- 不要在
ngAfterViewInit
中更新值。你可以在其他地方更新,比如用户点击了某个按钮,或者做了其他操作时。 - 使用
ChangeDetectorRef
手动触发变更检测。既然Angular不让我们在变更检测之后更新值,那么我们就在更新值以后手动触发一次变更检测,如下:1
2
3
4
5constructor(private cdr: ChangeDetectorRef) {}
ngAfterViewInit() {
this.count = 2;
this.cdr.detectChanges();
} - 使用异步更新,比如用
setTimeout
,Promise
等包裹一下更新值的操作。因为Angular内部使用zone.js
对这些异步操作进行了hook,并自动加入了更新检测,所以这样做是安全的。1
2
3
4
5
6
7
8
9
10
11
12
13ngAfterViewInit() {
setTimeout(() => {
this.count = 2;
});
}
// or
ngAfterViewInit() {
Promise.resolve().then(() => {
this.count = 2;
});
}
Use random values in template bindings
1 | <!-- app.component.html --> |
1 | // app.component.ts |
这个例子中,我们在模板中使用了一个随机值,每次变更检测都会重新计算这个值,所以就会产生这个错误。
Update parent component property from child component
We declare a name property in parent component and show it on html template.
1 | // parent.component.ts |
1 | <!-- parent.component.html --> |
Then we update the name property in child component.
1 | <!-- child.component.html --> |
Run the application, you will see the error in the console.
1 | core.mjs:9171 ERROR Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'Tom'. Current value: 'Jerry' |
Conclusion
There are several scenarios that produce this error.
- value in
ngAfterViewInit
. - Use random values in template bindings
- Update parent component property from child component.
How to solve this problem
- Refactor
ngAfterViewInit
lifecycle hook. - Use
ChangeDetectorRef
to manually trigger change detection. - Make getters idempotent.
- Make the update async(last resort)
Reference
https://angular.io/errors/NG0100
后记
话说我现在实在太困了,睁不开眼睛,写这破玩意能涨工资吗?大概率不能!但是老话儿说的好,凡事求个明白!不扯了,睡觉去了。