和Angular打交道的过程中大概率会遇到ExpressionChangedAfterItHasBeenCheckedError
,这个异常通常只在开发调试时会被抛出,生产环境里会被'吞掉'
, 虽然生产环境中看不到这个异常,但这并不意味着不需要处理这个问题; 出现这个异常可能导致ui上显示的数据和实际的不同步. 我在初学Angular时到现在已经多次遇到这个问题了, 所以在这里记录一下.
问题复现
通过Angular CLI创建一个Angular项目, 并准备两个组件AComponent
和BComponent
, AComponent
是BComponent
父组件; 然后创建一个SharedService
, 这个SharedService
包含两个属性title
和name
.
SharedService
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class SharedService {
title = '哈哈哈哈哈,我是title';
name = '一个名字';
constructor() { }
}
在AComponent
和BComponent
中都注入SharedService
, 同时在子组件BComponent
的ngOnInit
方法中改变SharedService
属性值
AComponent
:
import { SharedService } from './../shared.service';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-a',
template: `<h2>{{ sharedServ.title }}</h2>
<p>{{ sharedServ.name }}</p>
<app-b></app-b>`,
styleUrls: ['./a.component.css'],
})
export class AComponent implements OnInit {
constructor(public sharedServ: SharedService) {}
ngOnInit(): void {}
}
BComponent
:
import { SharedService } from './../shared.service';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-b',
template: `<p>{{ sharedServ.title }}</p>`,
styleUrls: ['./b.component.css'],
})
export class BComponent implements OnInit {
constructor(public sharedServ: SharedService) {
}
ngOnInit(): void {
this.sharedServ.title = '标题被修改了';
}
}
ng serve
运行便可以在浏览器控制台看到ExpressionChangedAfterItHasBeenCheckedError
异常
这里我只是举了一个简单的例子, 实际情况可能比这要复杂很多, 但是大抵都是在组件中更新了父组件上模板中绑定的变量有关...
解决方案
解决方案有多种, 这里只记录其中一种我用起来比较顺手的;
修改子组件ngOnInit
方法中更新SharedService
属性值的代码, 同步更新改为巧用Promise
进行异步更新:
ngOnInit(): void {
Promise.resolve(null).then(() => {
this.sharedServ.title = '标题被修改了';
});
}
为什么同步更新改为异步更新后就不会出现ExpressionChangedAfterItHasBeenCheckedError
的异常了呢? 简单来说就是异步更新的代码需要等待当前同步执行的代码执行完后, 才会执行. 这就使得Angular在本次变更检测中校验属性值时得以成功, 详细的变更检测的过程以及Angular为什么要防止属性值在一次变更检测被更改后面再有时间在记录.
参考
Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error