ExpressionChangedAfterItHasBeenCheckedErrorとは
子コンポーネントに値を渡した後に親コンポーネントの値が変わってたときに出てくるエラーみたいです。
エラーになる原因
Angular2の変更検知(change detection)
Angular2の変更検知オペレーションを理解しないとエラーになる原因がすっかり理解できないため、その処理を簡単にまとめてみます。
変更検知処理
チェックプロセス1
- 子コンポーネント・ディレクティブにバウンディングしているプロパティを更新する
- 子コンポーネントのngOnInit, onChanges, ngDoCheckをinvokeする
- 現在のDOMを更新する
- 子コンポーネントの変更検知処理(チェックプロセス1再帰)を実行する
- 子コンポーネントのngAfterViewInitをinvokeする
引用元:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError
error
各ステップの各プロパティはコンポーネントのoldValuesプロパティに保存しています。上記の処理が終わったら、下記の処理(digest cycle)を行い、oldValuesに保存された値と現在コンポーネントの値の比較が行われます:
チェックプロセス2
- oldValuesに保存された子コンポーネント用プロパティと現在コンポーネントもつ値が一致かどうかをチェック
- oldValuesに保存された現在のDOMを更新プロパティと現在コンポーネント持つ値が一致かどうかをチェック
- 子コンポーネントにチェックプロセス2を実行する
で、下記の例でエラーになる原因を説明させていただきます。
例えば、コンポーネントAと子コンポーネントBがあります。
コンポーネントAが子コンポーネントBに"text"プロパティを渡したい場合は:
- コンポーネントAのチェックプロセス1を実行する(textプロパティをoldValuesに記録する)
- コンポーネントBのチェックプロセス1を実行する(ここでおやコンポーネントAのtextプロパティが変った、理由は後ほど)
- コンポーネントAのチェックプロセス2を実行する
- oldValuesに記録されたtextプロパティと現在コンポーネントA持つtextプロパティが不一致のため、処理が中止され、ExpressionChangedAfterItHasBeenCheckedErrorがスローされます。
プロパティが変った原因
色々なケースがありますが、下記のようにコンポーネントBのngOnInit()で、おやコンポーネントAのtextプロパティを強制的に変更する例があります。:
export class BComponent {
@Input() text;
constructor(private parent: AppComponent) {}
ngOnInit() {
this.parent.text = 'updated text';
}
}
引用元:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError
error
解決策
export class BComponent {
@Input() text;
constructor(private parent: AppComponent) {}
ngOnInit() {
// setTimeoutで、コンポーネントAのチェックプロセス2を先に実行させてから、おやコンポーネントAのプロパティ値を変更してもエラーにならない(チェック処理後値を変更するからだ)
setTimeout(this.parent.text = 'updated text', 1000);
}
}
まとめ
- Angular2の変更検知処理がこのエラーになる原因です。
- 子コンポーネントのライフサイクル・フックでおやコンポーネントのプロパティを変更するのは危ないです。