🅰️

ngAfterViewInitでプロパティを変更したときに怒られたら

2024/01/01に公開

概要

AngularでコンポーネントのプロパティをngAfterViewInit内で変更した場合、NG0100: Expression has changed after it was checkedというエラーが出て怒られる場合があります。
このままでも特に問題なく動きはしますが、気持ち悪いので消そうと思ったら調べるのに割と手間取ったので備忘録です。

結論

[Angular] (EXCEPTION: Expression has changed after it was checked) の対応方法 (Angular2, 4, 5) #Angular - Qiitaに記載の通りですが、少し古い記事なのでinject()等を使って現代的に書き直してみます。
ChangeDetectorRefを使って明示的に変更検知を呼び出してあげればいいだけです。

export class TestComponent implements AfterViewInit {
    testProperty = 0;
    private cd = inject(ChangeDetectorRef);
    
    ngAfterViewInit(): void {
        testProperty = 1;
	this.cd.detectChanges();
    }
}

追記(2024/01/10)

以下のような状況下でも同じエラーが発生してしまいました。

  • コンポーネントのプロパティにMapオブジェクトを保持
    • Mapの採用理由としては反復時の順序を固定する必要があったため
  • APIからのレスポンスを非同期に受け取り、ngOnInitなどで非同期にMapオブジェクトを更新
  • Mapオブジェクトの要素をentries()メソッドを使い、NgForで回す

以下、エラーが発生する場合の簡単なサンプルです。

test.component.ts
export class TestComponent implements onInit {
    testProperty:Map<string,string> = new Map(); // テンプレートで使いたいMap
    private exampleService = inject(ExampleService); // 何かAPIと通信するサービス
    
    ngOnInit(): void {
        // 非同期でレスポンスを受け取る
        this.exampleService.getApiResponse().then((res)=>{
	    const body = res.body;
	    // 受け取ったレスポンスを元に、色々な処理をしてMapオブジェクトを生成
	    this.testProperty = resultMapObject; // 生成したMapオブジェクトを代入
	})
    }
}
test.compoent.html
<div *ngFor="let data of testProperty.entries()">
  <div>{{ data[0] }}</div>
  <div>{{ data[1] }}</div>
</div>

どうもコンポーネント生成時のtestPropertyとレスポンスを受け取ったあとのtestPropertyから作られるイテレータが異なるので怒られているようです。これは単純に上記の手動変更検知を行ってもダメでした。

解決策としては、Mapオブジェクトを生成してからコンポーネントのプロパティに代入する際にタプル型の配列[1]にしてしまえばOKでした。

test.component.ts
export class TestComponent implements onInit {
    testProperty:[string,string][] = []; // タプル型の配列にする
    private exampleService = inject(ExampleService);
    
    ngOnInit(): void {
        this.exampleService.getApiResponse().then((res)=>{
	    const body = res.body
	    // 受け取ったレスポンスを元に、色々な処理をしてMapオブジェクトを生成
	    this.testProperty = [...resultMapObject]; // Mapを配列にする
	})
    }
}
test.compoent.html
<div *ngFor="let data of testProperty">
  <div>{{ data[0] }}</div>
  <div>{{ data[1] }}</div>
</div>
脚注
  1. 参考:Map<K, V> | TypeScript入門『サバイバルTypeScript』 ↩︎

Discussion