ExpressionChangedAfterItHasBeenCheckedErrorの説明


308

このエラーが発生し続ける理由を教えてください。 ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

明らかに、私はそれを開発モードでのみ取得しますが、本番ビルドでは発生しませんが、それは非常に煩わしく、開発環境でエラーが発生してプロダクションに表示されないことの利点を理解できません- -おそらく私の理解不足のためです。

通常、修正は簡単で、エラーの原因となるコードを次のようにsetTimeoutでラップするだけです。

setTimeout(()=> {
    this.isLoading = true;
}, 0);

または、次のようなコンストラクタで強制的に変更を検出します。constructor(private cd: ChangeDetectorRef) {}

this.isLoading = true;
this.cd.detectChanges();

しかし、なぜ私は常にこのエラーに遭遇するのですか?私はそれを理解したいので、将来これらのハックな修正を避けることができます。


回答:


121

同様の問題がありました。見てみると、ライフサイクルフックドキュメンテーション、私は変更ngAfterViewInitするngAfterContentInitと、それが働きました。


@PhilipEnc私の問題は、DOMの変更によって引き起こされた変更に関連していました。DOMが変更されると、(@ ContentChildrenプロパティから取得された)QueryListオブジェクトが更新され、更新によって呼び出されたメソッド内で、双方向のバインドされたプロパティが変更されます。これは私が抱えていた問題を引き起こしました。setTimeout上記のように2つのプロパティへの変更をラップすることでうまくいきました。ありがとう!
kbpontius 2018

1
私の場合、primingグリッド配列の値を変更するコードをngAfterContentInitに配置しましたが、コードをngOnInitに配置すると機能しました。
Vibhu

ngAfterContentCheckedngAfterContentInitまだエラーをスローしながら、ここで動作します。
ashubuntu

ngAfterContentCheckedを使用していますが、プロジェクトのロードが非常に遅い
Ghotekar Rahul

101

このエラーはアプリケーションの実際の問題を示しているため、例外をスローすることは理にかなっています。

devMode変化検出モデルが変更されたかどうかを確認するために、すべての定期的な変更検出の実行後に追加のターンを追加します。

通常の変更検出ターンと追加の変更検出ターンの間でモデルが変更された場合、これは、

  • 変更検出自体が変更を引き起こした
  • メソッドまたはゲッターは、呼び出されるたびに異なる値を返します

これはどちらも悪いことです。モデルが安定しない可能性があるため、処理方法が明確でないためです。

Angularがモデルが安定するまで変更検出を実行すると、永久に実行される可能性があります。Angularが変更検出を実行しない場合、ビューはモデルの現在の状態を反映していない可能性があります。

参照してくださいAngular2の生産と開発モードの違いは何ですか?


4
今後このエラーが発生しないようにするにはどうすればよいですか?同じ間違いを犯さないようにするためにコードについて考える必要がある別の方法はありますか?
Kevin LeStarge 2017

25
通常、これはのようないくつかのライフサイクルコールバックによって引き起こされるngOnInitか、ngOnChanges(一部のライフサイクルコールバックは、他の人が、私は1つが行うかないかを正確に自分を覚えていないしていないモデルを変更できるように)モデルを修正します。ビューのメソッドや関数にバインドせず、代わりにフィールドにバインドしてイベントハンドラーのフィールドを更新します。メソッドにバインドする必要がある場合は、実際に変更がない限り、メソッドが常に同じ値のインスタンスを返すことを確認してください。変更検出はこれらのメソッドを頻繁に呼び出します。
ギュンターZöchbauer

:NGX-トースターライブラリを使用して、このエラーを誰がここに到着し、誰のために、ここでのバグレポートですgithub.com/scttcper/ngx-toastr/issues/160
rmcsharry

2
それは必ずしもアプリの問題ではありません。呼び出しchangeRef.detectChanges()が解決策である/エラーを抑制するという事実は、これの証拠です。これ$scope.$watch()は、Angular 1で状態を変更するようなものです
ケビンビール

1
私はAngular 1をよく知りませんが、Angular 2での変更検出の動作はまったく異なります。それは必ずしも問題ではないが、通常cdRef.detectChanges()は一部の奇妙なエッジケースでのみ必要であり、必要に応じて注意深く調べ、理由を正しく理解する必要があります。
ギュンターZöchbauer

83

Angular Lifecycle Hooksと変更検出との関係を理解すると、多くの理解が得られました。

Angularに*ngIf要素のにバインドされたグローバルフラグを更新させようとしていてngOnInit()、別のコンポーネントのライフサイクルフック内でそのフラグを変更しようとしました。

ドキュメントによると、このメソッドはAngularがすでに変更を検出した後に呼び出されます:

最初のngOnChanges()の後で1回呼び出されます。

したがって、内部のフラグを更新しても、ngOnChanges()変更の検出は開始されません。次に、変更検出が再び自然にトリガーされると、フラグの値が変更され、エラーがスローされます。

私の場合、私はこれを変更しました:

constructor(private globalEventsService: GlobalEventsService) {

}

ngOnInit() {
    this.globalEventsService.showCheckoutHeader = true;
}

これに:

constructor(private globalEventsService: GlobalEventsService) {
    this.globalEventsService.showCheckoutHeader = true;
}

ngOnInit() {

}

そしてそれは問題を修正しました:)


3
私の問題も同様でした。長い時間の後で間違いを犯し、ngOnInit関数とコンストラクタの外で変数を定義したこと。これは、初期化関数に配置されたオブザーバブルからデータ変更を受け取ります。あなたと同じことをしてエラーを修正しました。
ravo10

1
全体的に非常に似router.navigateていますが、URLに存在する場合、フラグメントにロードするときにスクロール()しようとしました。このコードは、最初AfterViewInitはエラーを受け取る場所に配置されていました。その後、あなたが言うようにコンストラクターに移動しましたが、フラグメントは考慮されていませんでした。ngOnInit解決に移動:)ありがとうございます!
Joel Balmer、

私のhtmlがget ClockValue(){return DateTime.TimeAMPM(new Date())を介して "HH:MM"として時間を返すゲッターにバインドされている場合、検出の実行中に分が変化すると、最終的にトリップします。どうすればよいですか。これを修正しますか?
Meryan

こっちも一緒。またsetInterval()、他のライフタイムイベントコードの後に​​起動する必要がある場合にも機能することがわかりました。
Rick Strahl

39

更新

最初にOPの自己応答から始めることを強くお勧めします。VSで実行できることとで実行するconstructor必要があることを適切に検討してくださいngOnChanges()

元の

これは答えというよりは副次的なものですが、誰かを助けるかもしれません。ボタンの存在をフォームの状態に依存させようとしたときに、この問題に遭遇しました。

<button *ngIf="form.pristine">Yo</button>

私の知る限り、この構文により、ボタンは条件に基づいてDOMに追加および削除されます。これは順番ににつながりExpressionChangedAfterItHasBeenCheckedErrorます。

私の場合の修正は(違いの完全な影響を把握しているとは言いませんが)、display: none代わりに使用することでした:

<button [style.display]="form.pristine ? 'inline' : 'none'">Yo</button>

6
ngIfとスタイルの違いについての私の理解は、ngIfは条件がtrueになるまでページにHTMLを含めないため、スタイル手法によりHTMLが常にページ内にあり、form.pristineの値に基づいて単に非表示または表示されます。
user3785010 2017

4
[hidden]非常に詳細な[style.display]部分の代わりに使用することもできます。:)
Philipp Meissner

2
何故なの。けれども、このページ上の別のコメントで@Simon_Weaverで述べたように、[hidden] 常に同じ動作がありませんようにdisplay: none
アルノーP

1
各ボタンに* ngIfを使用して2つの異なるボタン(ログアウト/ログイン)を表示していて、これが問題の原因でした。
GoTo 2018年

コンストラクターは私にとって適切な場所であり、重要なスナックバーを立ち上げました
オースティン

31

興味深い答えがありましたが、自分のニーズに一致するものを見つけられなかったようです。最も近いのは、1つの特定の機能のみを参照し、アプリのように複数のトグルを参照しない@ chittrang-mishraからのものです。

私はDOMの一部でもない[hidden]ことを利用するために使用したくなかった*ngIfので、次の解決策を見つけました。エラーを修正するのではなく抑制するので、すべてに最適ではない可能性がありますが、私の場合は最終結果は正しいですが、私のアプリでは問題ないようです。

私がしたことは、実装AfterViewCheckedして追加constructor(private changeDetector : ChangeDetectorRef ) {}してから

ngAfterViewChecked(){
  this.changeDetector.detectChanges();
}

他の多くの人が私を助けてくれたので、これが他の人を助けることを願っています。


3
これは無限変化検出ループを引き起こしませんか?つまり、チェックした後で変更を検出しています。
Manuel Azar

@ManuelAzarどうやらそうではない。これは私のために働いた唯一の解決策です。最後に、コンソールで少し沈黙をしました。私はこれらすべての無関係な変更検出「エラー」にうんざりしていました。
Jeremy Thille

31

Angularは変更検出を実行し、子コンポーネントに渡されたいくつかの値が変更されたことが判明した場合、Angularはエラーをスローします。

ExpressionChangedAfterItHasBeenCheckedError 詳細はこちら

これを修正するために、AfterContentCheckedライフサイクルフックと

import { ChangeDetectorRef, AfterContentChecked} from '@angular/core';

  constructor(
  private cdref: ChangeDetectorRef) { }

  ngAfterContentChecked() {

    this.cdref.detectChanges();

  }

これで問題が解決する可能性がありますが、これは非常に拡張性があり、CDを酷使しすぎていませんか?
ニッキー

これが、子に値を渡すことによって引き起こされるこのエラーに対処する唯一の答えだと思います。ありがとう!
java-addict301

@ニッキーはい。画面をタッチするたびに、どこでもngAfterContentChecked()が呼び出されます
Mert Mertce

25

私の場合、テストの実行中にスペックファイルでこの問題が発生しました。

に変更する必要がありngIf ました [hidden]

<app-loading *ngIf="isLoading"></app-loading>

<app-loading [hidden]="!isLoading"></app-loading>


2
ここでの違いは*ngIf、DOM を変更し、ページに要素を追加および削除する一方[hidden]で、アイテムをDOMから削除せずに可視性を変更することです。
グルンゴンドーラ2018年

5
しかし、これは実際の問題を実際に解決しませんでした...?
ravo10

23

以下の手順に従ってください:

1.次のように@ angular / coreからインポートして「ChangeDetectorRef」を使用します。

import{ ChangeDetectorRef } from '@angular/core';

2.以下のように、それをconstructor()に実装します。

constructor(   private cdRef : ChangeDetectorRef  ) {}

3.ボタンのクリックなどのイベントで呼び出す関数に次のメソッドを追加します。したがって、次のようになります。

functionName() {   
    yourCode;  
    //add this line to get rid of the error  
    this.cdRef.detectChanges();     
}

23

私はng2-carouselamos(Angular 8とBootstrap 4)を使用していました

以下は私の問題を修正しました:

私がしたこと:

1. implement AfterViewChecked,  
2. add constructor(private changeDetector : ChangeDetectorRef ) {} and then 
3. ngAfterViewChecked(){ this.changeDetector.detectChanges(); }

それは役に立ちました。すごい!
Pathik Vejani

あなたは私の日を救った...ありがとう!
オモスタン

19

コンポーネントの配列の1つで値が変化するのと同じ問題に直面していました。しかし、値の変化に対する変化を検出する代わりに、コンポーネントの変化検出戦略をonPush(値の変化ではなくオブジェクトの変化に対する変化を検出する)に変更しました。

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush
    selector: -
    ......
})

これは、コントロールを動的に追加/削除するために機能するように見えました..欠点はありますか?
Ricardo Saracino、

手元にある状況で魅力のように動作します、ありがとう!コンポーネントが「グローバル」オブジェクトにバインドされたため、他の場所で変更され、エラーが発生しました。このコンポーネントには、バインドされたオブジェクトが更新されたときの更新ハンドラーがすでにありました。このイベントハンドラーは、changeDetectorRef.detectChanges()をChangeDetectionStrategy.OnPushと組み合わせて呼び出します。
ベルヌーイIT

@RicardoSaracino欠点はありましたか?私も同じことを考えていました。変更検出OnPushがどのように機能するかは知っていますが、見落としている問題があるのではないかと考えています。戻る必要はありません。
mtpultz

@RicardoSaracino、はい、いくつかの欠点があります。この詳細なリンクを参照できます。blog.angular
university.io/

@BernoulliITありがとう、それがあなたのために働いたことを嬉しく思います。
Dheeraj

17

記事を参照してくださいhttps://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

したがって、変更検出の背後にあるメカニズムは、実際には、変更検出と検証ダイジェストの両方が同期して実行されるように機能します。つまり、プロパティを非同期で更新しても、検証ループの実行時に値は更新されず、ExpressionChanged...エラーも発生しません。このエラーが発生する理由は、検証プロセス中に、Angularが変更検出フェーズ中に記録した値とは異なる値を確認するためです。それを避けるために....

1)changeDetectorRefを使用する

2)setTimeOutを使用します。これにより、別のVMでマクロタスクとしてコードが実行されます。Angularは検証プロセス中にこれらの変更を認識せず、そのエラーは発生しません。

 setTimeout(() => {
        this.isLoading = true;
    });

3)同じVMでコードを実行したい場合は、

Promise.resolve(null).then(() => this.isLoading = true);

これにより、マイクロタスクが作成されます。マイクロタスクキューは、現在の同期コードの実行が終了した後に処理されるため、プロパティの更新は検証ステップの後に行われます。


オプション#3をスタイル式で使用できますか?注入されたコンテンツに基づいているため、最後に評価する必要がある高さのスタイル式があります。
N-ate

1
申し訳ありませんが、コメントを見ただけです。理由がわかりません。したがって、スタイルの変更でも機能するはずです。
ATHER、2018

4

@HostBinding このエラーは、混乱を招く可能性があります。

たとえば、コンポーネントに次のホストバインディングがあるとします。

// image-carousel.component.ts
@HostBinding('style.background') 
style_groupBG: string;

簡単にするために、このプロパティが次の入力プロパティを介して更新されるとしましょう:

@Input('carouselConfig')
public set carouselConfig(carouselConfig: string) 
{
    this.style_groupBG = carouselConfig.bgColor;   
}

親コンポーネントでは、プログラムで設定しています ngAfterViewInit

@ViewChild(ImageCarousel) carousel: ImageCarousel;

ngAfterViewInit()
{
    this.carousel.carouselConfig = { bgColor: 'red' };
}

ここで何が起こるかです:

  • 親コンポーネントが作成されます
  • ImageCarouselコンポーネントが作成され、carousel(ViewChildを介して)に割り当てられます
  • (nullになります)carouselまでアクセスできませんngAfterViewInit()
  • 設定を割り当てる、設定します style_groupBG = 'red'
  • これbackground: redにより、ホストのImageCarouselコンポーネントが設定されます。
  • このコンポーネントは親コンポーネントによって「所有」されているため、変更をチェックすると、変更が検出されcarousel.style.background、問題ではないことを認識するのに十分ではないため、例外がスローされます。

1つの解決策は、ImageCarouselに別のラッパーdivインサイダーを導入し、その上に背景色を設定することですが、使用するといくつかの利点が得られません HostBinding(親がオブジェクトの完全な境界を制御できるようにするなど)。

親コンポーネントでのより良い解決策は、設定を設定した後にdetectChanges()を追加することです。

ngAfterViewInit()
{
    this.carousel.carouselConfig = { ... };
    this.cdr.detectChanges();
}

これは、このように設定された非常に明白なように見え、他の回答と非常に似ていますが、微妙な違いがあります。

@HostBinding開発中の後半まで追加しない場合を検討してください。突然あなたはこのエラーを受け取り、それは意味をなさないようです。


2

これが何が起こっているかについての私の考えです。私はドキュメントを読んでいませんが、これがエラーが表示される理由の一部であると確信しています。

*ngIf="isProcessing()" 

* ngIfを使用する場合、条件が変化するたびに要素を追加または削除することにより、DOMを物理的に変更します。したがって、ビューにレンダリングされる前に条件が変更されると(Angularの世界では非常に可能性があります)、エラーがスローされます。説明を参照してくださいここでは、開発と生産のモード間。

[hidden]="isProcessing()"

使用[hidden]する場合、物理的には変更されませんDOMが、単にelementビューから隠さCSSれます。おそらく、背面で使用されます。要素は引き続きDOMに存在しますが、条件の値によっては表示されません。そのため、を使用してもエラーは発生しません[hidden]


場合はisProcessing()雨のことをやっている、あなたが使用する必要があります!isProcessing()のために[hidden]
マチューシャルボニエ

hidden「背面でCSSを使用する」のではなく、通常のHTMLプロパティです。developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/...
ラザーLjubenović

1

私の問題のために、私は読んでいたgithubの「afterViewInit内のコンポーネント『非モデル』の値を変更するときにExpressionChangedAfterItHasBeenCheckedErrorを」とngModelを追加することにしました-

<input type="hidden" ngModel #clientName />

それは私の問題を修正しました、それが誰かを助けることを望みます。


1
そのサイトのどこに追加するように言っていますかngModel。また、これが役立つ理由を詳しく説明してください。
Peter Wippermann、

この問題を追跡していたときに、リンクを調査するようになりました。記事を読んだ後、属性を追加し、問題を修正しました。誰かが同じ問題に遭遇した場合に役立ちます。
Demodave 2018

1

デバッグのヒント

このエラーは非常に混乱する可能性があり、正確にいつ発生するかについて誤った仮定をするのは簡単です。影響を受けるコンポーネント全体の適切な場所に、このような多くのデバッグステートメントを追加すると便利です。これはフローの理解に役立ちます。

親のputステートメントは次のようになります(正確な文字列 'EXPRESSIONCHANGED'が重要です)が、それ以外はこれらは単なる例です。

    console.log('EXPRESSIONCHANGED - HomePageComponent: constructor');
    console.log('EXPRESSIONCHANGED - HomePageComponent: setting config', newConfig);
    console.log('EXPRESSIONCHANGED - HomePageComponent: setting config ok');
    console.log('EXPRESSIONCHANGED - HomePageComponent: running detectchanges');

子/サービス/タイマーコールバック:

    console.log('EXPRESSIONCHANGED - ChildComponent: setting config');
    console.log('EXPRESSIONCHANGED - ChildComponent: setting config ok');

detectChanges手動で実行する場合は、そのためのログも追加します。

    console.log('EXPRESSIONCHANGED - ChildComponent: running detectchanges');
    this.cdr.detectChanges();

次に、Chromeデバッガーで「EXPRESSIONCHANGES」でフィルターします。これにより、設定されるすべてのフローと順序が正確に示され、Angularがエラーをスローするポイントが正確に示されます。

ここに画像の説明を入力してください

グレーのリンクをクリックして、ブレークポイントを配置することもできます。

アプリケーション全体で同様の名前のプロパティ(などstyle.background)を使用している場合は、もう1つ注意が必要なのは、意図しないプロパティをデバッグしていることを確認するために、あいまいな色の値に設定することです。


1

私の場合、LoadingServiceBehaviouralSubject で非同期プロパティがありましたisLoading

[非表示]モデルの使用は機能しますが、* ngIfは失敗します

    <h1 [hidden]="!(loaderService.isLoading | async)">
        THIS WORKS FINE
        (Loading Data)
    </h1>

    <h1 *ngIf="!(loaderService.isLoading | async)">
        THIS THROWS ERROR
        (Loading Data)
    </h1>

1

rxjsを使用して私のために働いたソリューション

import { startWith, tap, delay } from 'rxjs/operators';

// Data field used to populate on the html
dataSource: any;

....

ngAfterViewInit() {
  this.yourAsyncData.
      .pipe(
          startWith(null),
          delay(0),
          tap((res) => this.dataSource = res)
      ).subscribe();
}

問題のあるコードは何でしたか?ここでの解決策は何ですか?
mkb

こんにちは@mkb ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.DOMが変更されたときに値の変更がトリガーされたときに問題が発生しました
Sandeep K Nair

こんにちは、問題を克服するためにここで何をしましたか。以前にrxjsをまったく使用していなかったか、delay()を追加したか、startWith()を追加しましたか?私はすでにさまざまなrxjsメソッドでrxjsを使用していますが、それでもエラーが発生します。
ミステリアス

追加されdelayたエラーはなくなります。と同様に機能しsetTimeoutます。
LazarLjubenović

1

Ionic3(テクノロジースタックの一部としてAngular 4を使用)でこの種のエラーが発生しました。

私にとってはこれをやっていました:

<ion-icon [name]="getFavIconName()"></ion-icon>

そのため、画面で操作しているモードごとに、イオンアイコンのタイプpinをa からに条件付きで変更しようとしremove-circleました。

*ngIf代わりにを追加する必要があると思います。


1

追加し*ngIfたときに私の問題は明白でしたが、それは原因ではありませんでした。このエラーは、{{}}タグ内のモデルを変更し、変更されたモデルを*ngIf後でステートメントで表示しようとしたために発生しました。次に例を示します。

<div>{{changeMyModelValue()}}</div> <!--don't do this!  or you could get error: ExpressionChangedAfterItHasBeenCheckedError-->
....
<div *ngIf="true">{{myModel.value}}</div>

この問題を解決するために、電話changeMyModelValue()の場所をよりわかりやすい場所に変更しました。

私の状況ではchangeMyModelValue()、子コンポーネントがデータを変更するたびに呼び出されるようにしました。これには、親がそれを処理できるように、子コンポーネントでイベントを作成して発行する必要がありました(を呼び出すことによって。https://angular.io/guide/component-interaction#parent-listens-for-child-eventをchangeMyModelValue()参照してください)


0

これが誰かがここに来るのに役立つことを願っています。ngOnInit次の方法でサービス呼び出しを行い、変数displayMainを使用してDOMへの要素のマウントを制御します。

component.ts

  displayMain: boolean;
  ngOnInit() {
    this.displayMain = false;
    // Service Calls go here
    // Service Call 1
    // Service Call 2
    // ...
    this.displayMain = true;
  }

およびcomponent.html

<div *ngIf="displayMain"> <!-- This is the Root Element -->
 <!-- All the HTML Goes here -->
</div>

0

component.tsで宣言されていないcomponent.htmlの変数を使用していたため、このエラーが発生しました。HTMLの一部を削除すると、このエラーはなくなりました。


0

モーダルでreduxアクションをディスパッチしていて、そのときにモーダルが開かれていないため、このエラーが発生しました。モーダルコンポーネントが入力を受け取った瞬間に、アクションをディスパッチしていました。したがって、モーダルが開かれ、アクションがディスパッチされることを確認するために、そこにsetTimeoutを配置します。


0

これで苦労している人に。このエラーを正しくデバッグする方法を次に示します。https//blog.angular-university.io/angular-debugging/

私の場合、確かに私は* ngIfの代わりにこの[hidden]ハックを使用してこのエラーを取り除きました...

しかし、私が提供したリンクにより、私はTHE GUILTY * ngIf :) を見つけることができました。

楽しい。


hidden代わりにを使用することはngIfハックではなく、問題の核心にまったく対処しません。あなたはただ問題を隠しているだけです。
LazarLjubenović

-2

ソリューション...サービスとrxjs ...イベントエミッターとプロパティバインディングはどちらもrxjsを使用します。自分で実装する方が優れており、より制御しやすく、デバッグが容易です。イベントエミッターはrxjsを使用していることに注意してください。単に、サービスを作成してオブザーバブル内で、各コンポーネントにオブザーバーをサブスクライブさせ、必要に応じて新しい値またはcosume値を渡します


1
これは質問に答えないだけでなく、ひどいアドバイスでもあります。皆さん、AngularのCDエラーが発生しているからといって、rxjsを自分で再実装しないでください。:)
LazarLjubenović
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.