この記事では、Angularの開発中によく遭遇するエラーの一つである『ExpressionChangedAfterItHasBeenCheckedError』について、以下の内容をサンプルコードを用いてわかりやすく解説します。
ExpressionChangedAfterItHasBeenCheckedError
とはExpressionChangedAfterItHasBeenCheckedError
の解決方法
ExpressionChangedAfterItHasBeenCheckedErrorとは
Angularの変更検知(Change Detection)中にコンポーネントのデータバインディングが予期せず変更されると、Angularは「既にチェック済みの値が変更された」と認識し、ExpressionChangedAfterItHasBeenCheckedError
が発生します。これは、Angularの開発中によく遭遇するエラーの一つです。
Angularはビュー(画面)を更新するために「変更検知」という作業を行います。変更検知の開始時に、Angularはコンポーネントのデータバインディングをチェックし、ビューが最新の状態であることを確認します。変更検知の終了時にも、再びコンポーネントのデータバインディングの値が変更されていないかをチェックします。この最終チェックの段階で、既にチェック済みの値が変更されていると、ExpressionChangedAfterItHasBeenCheckedError
が発生します。
例えば、以下のような場合でExpressionChangedAfterItHasBeenCheckedError
が発生します。
- ライフサイクルフック内での値変更
ngAfterViewInit
などのライフサイクルフック内でプロパティの値を変更した場合。
- 親から子コンポーネントに渡した値の変更
- 親コンポーネントが子コンポーネントに値を渡した後、その値がさらに変更された場合。
- 非同期操作
- タイマーやAPI呼び出しの結果、データが更新された場合。
各場合においてExpressionChangedAfterItHasBeenCheckedError
を発生させてみましょう。
補足
変更検知の終了時にコンポーネントのデータバインディングの値が変更されていないかをチェックするのは、開発モードのみです。そのため、ExpressionChangedAfterItHasBeenCheckedError
は開発モードでのみ発生します。
開発モードでは、アプリケーションが正しく動作しているかを検証するために、各変更検知の終了後に追加のチェックを実行してコンポーネントのデータバインディングが変更されていないことを確認しています。
ライフサイクルフック内での値変更
ExpressionChangedAfterItHasBeenCheckedError
を発生させるサンプルコードを以下に示します。
import { Component, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div>
<p>カウント: {{ count }}</p>
<button (click)="increment()">Increment</button>
</div>
`
})
export class AppComponent implements AfterViewInit {
count = 0;
ngAfterViewInit(): void {
// AfterViewInitの後に値を変更すると、ExpressionChangedAfterItHasBeenCheckedErrorが発生
this.count = 1;
}
increment(): void {
this.count++;
}
}
ngAfterViewInit
はビューが描画された直後に何かしらの処理を行うためのライフサイクルフックです。すなわち、ngAfterViewInit
内の処理が実行される前には、Angularはすでにコンポーネントのデータバインディングの値をチェックし、コンポーネントの状態をレンダリングしています。
そのため、ngAfterViewInit
内でコンポーネントのデータバインディングの値を変更すると、Angularは「既にチェック済みの値が変更された」と認識し、ExpressionChangedAfterItHasBeenCheckedError
が発生します。
ExpressionChangedAfterItHasBeenCheckedErrorの解決方法
ExpressionChangedAfterItHasBeenCheckedError
を防ぐ解決策としては、以下の方法があります。
- Angularのライフサイクルチェック外で値を変更する
ChangeDetectorRef
を使用して手動で変更検知を実行する
Angularのライフサイクルチェック外で値を変更する
例えば、setTimeout
を使用すると、Angularのライフサイクルフック(例えば、ngOnInit
やngAfterViewChecked
など)の処理が終わった後に値を変更することができるので、エラーを回避することができます。サンプルコードを以下に示します。
ngAfterViewInit(): void {
setTimeout(() => {
this.count = 1;
});
}
ChangeDetectorRefを使用して手動で変更検知を実行する
ChangeDetectorRef
を使用すると、Angularに手動で状態変更を認識させることができ、エラーを回避することができます。サンプルコードを以下に示します。
import { AfterViewInit, ChangeDetectorRef, Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div>
<p>カウント: {{ count }}</p>
<button (click)="increment()">Increment</button>
</div>
`,
})
export class AppComponent implements AfterViewInit {
constructor(private cdr: ChangeDetectorRef) {}
count = 0;
ngAfterViewInit(): void {
this.count = 1;
this.cdr.detectChanges();
}
increment(): void {
this.count++;
}
}
親コンポーネントが子コンポーネントに値を渡した後にその値が変更された場合
ExpressionChangedAfterItHasBeenCheckedError
を発生させるサンプルコードを以下に示します。
親コンポーネント (parent.component.ts)
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<div>
<h2>Parent Component</h2>
<button (click)="updateValue()">Update Value</button>
<app-child [value]="parentValue"></app-child>
</div>
`,
})
export class ParentComponent {
parentValue = 'Initial Value';
updateValue() {
this.parentValue = 'Updated Value';
setTimeout(() => {
this.parentValue = 'Updated Again After Change Detection';
}, 0);
}
}
子コンポーネント (child.component.ts)
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<div>
<h3>Child Component</h3>
<p>Value: {{ value }}</p>
</div>
`,
})
export class ChildComponent {
@Input() value!: string;
}
親コンポーネントにはparentValue
というプロパティがあり、子コンポーネントに@Input()
デコレーターを使用して渡しています。updateValue()
メソッドはボタンのクリックによって呼び出され、parentValue
を更新します。変更検出の終了後にsetTimeout()
を使って、parentValue
を変更しているため、ExpressionChangedAfterItHasBeenCheckedError
が発生します。
本記事のまとめ
この記事ではAngularの開発中によく遭遇するエラーの一つである『ExpressionChangedAfterItHasBeenCheckedError』について、以下の内容を説明しました。
ExpressionChangedAfterItHasBeenCheckedError
とはExpressionChangedAfterItHasBeenCheckedError
の解決方法
お読み頂きありがとうございました。