和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
