0%

NG0100: Expression has changed after it was checked

不知各位冰雪聪明的朋友们是否遇到过如下错误?

1
NG0100: Expression has changed after it was checked

大家在第一次遇到这个错误的时候是什么反应呢?反正我是一头雾水,今天我就带大家揭开这个错误的神秘面纱,我现在很困。。。非常不想写,但是我还是要写,因为我是一个有责任心的人。

1
为什么我的眼里常含泪水?因为我太困了。。。

言归正传!其实从这个错误的描述来看,聪明的你已经发现了,那就是在Angular刚刚进行完一次变更检测,你又更新了某个值。那么,有哪些场景会导致这个错误呢?我们依次来看。

先来看一个最简单的例子:

Update value in ngAfterViewInit

1
2
<!-- app.component.html -->
<div>{{ count }}</div>
1
2
3
4
// app.component.ts
ngAfterViewInit() {
this.count = 2;
}

这个例子中,我们在ngAfterViewInit生命周期钩子中更新了count的值,在ngAfterViewInit之前,Angular刚刚进行完一次变更检测,我们在ngAfterViewInit中更新了count的值,此时下一次变更检测还未开始,所以就产生了这个错误。

error

他来了,他来了,他迈着箭步走来了!
我们可以点击红框中的文件链接,查看到底哪个变量出了问题。

ng0100

既然错误已经发生了,那么该如何解决它呢?

  1. 不要在ngAfterViewInit中更新值。你可以在其他地方更新,比如用户点击了某个按钮,或者做了其他操作时。
  2. 使用ChangeDetectorRef手动触发变更检测。既然Angular不让我们在变更检测之后更新值,那么我们就在更新值以后手动触发一次变更检测,如下:
    1
    2
    3
    4
    5
    constructor(private cdr: ChangeDetectorRef) {}
    ngAfterViewInit() {
    this.count = 2;
    this.cdr.detectChanges();
    }
  3. 使用异步更新,比如用setTimeoutPromise等包裹一下更新值的操作。因为Angular内部使用zone.js对这些异步操作进行了hook,并自动加入了更新检测,所以这样做是安全的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    ngAfterViewInit() {
    setTimeout(() => {
    this.count = 2;
    });
    }

    // or

    ngAfterViewInit() {
    Promise.resolve().then(() => {
    this.count = 2;
    });
    }

Use random values in template bindings

1
2
<!-- app.component.html -->
<div>{{ randomValue }}</div>
1
2
3
4
// app.component.ts
get randomValue() {
return Math.random();
}

这个例子中,我们在模板中使用了一个随机值,每次变更检测都会重新计算这个值,所以就会产生这个错误。

Update parent component property from child component

We declare a name property in parent component and show it on html template.

1
2
3
4
5
// parent.component.ts
name: string;
constructor() {
this.name = 'Tom';
}
1
2
3
<!-- parent.component.html -->
<p>I'm Father, my name is: {{ name }}</p>
<app-child (onChangeName)="changeName($event)" [age]=30 [name]="'Philip'"></app-child>

Then we update the name property in child component.

1
2
3
4
5
<!-- child.component.html -->
@Output() onChangeName = new EventEmitter<string>();
ngOnInit(): void {
this.onChangeName.emit('Jerry');
}

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.

  1. value in ngAfterViewInit.
  2. Use random values in template bindings
  3. Update parent component property from child component.

How to solve this problem

  1. Refactor ngAfterViewInit lifecycle hook.
  2. Use ChangeDetectorRef to manually trigger change detection.
  3. Make getters idempotent.
  4. Make the update async(last resort)

Reference

https://angular.io/errors/NG0100

后记

话说我现在实在太困了,睁不开眼睛,写这破玩意能涨工资吗?大概率不能!但是老话儿说的好,凡事求个明白!不扯了,睡觉去了。