なぜこのエラーが出るのか説明してください:ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
明らかに、私はdevモードでそれを取得するだけです、それは私のプロダクションビルドでは起こりません、しかしそれは非常に厄介ですそして私は単にprodに現れない私のdev環境にエラーを持つ利点を理解しません - - おそらく私の理解不足のため。
通常、修正は簡単です。コードの原因となるエラーをsetTimeoutにラップするだけです。
setTimeout(()=> {
this.isLoading = true;
}, 0);
あるいは、次のようなコンストラクタで変更を強制的に検出します。constructor(private cd: ChangeDetectorRef) {}
:
this.isLoading = true;
this.cd.detectChanges();
しかし、なぜこのエラーに遭遇するのでしょうか。私はそれを理解したいので、将来これらのハックな修正を避けることができます。
the Angular Lifecycle Hooks と、変更検出との関係を理解すると、多くの理解が得られました。
要素の*ngIf
にバインドされているグローバルフラグを更新するためにAngularを取得しようとしていましたが、他のコンポーネントのngOnInit()
ライフサイクルフック内でそのフラグを変更しようとしていました。
ドキュメントによると、このメソッドはAngularが既に変更を検出した後に呼び出されます。
最初のngOnChanges()の後に一度呼び出されます。
そのため、ngOnChanges()
内のフラグを更新しても、変更の検出は開始されません。その後、いったん変更検出が自然に再びトリガされると、フラグの値が変更され、エラーがスローされます。
私の場合は、これを変更しました。
constructor(private globalEventsService: GlobalEventsService) {
}
ngOnInit() {
this.globalEventsService.showCheckoutHeader = true;
}
これに:
constructor(private globalEventsService: GlobalEventsService) {
this.globalEventsService.showCheckoutHeader = true;
}
ngOnInit() {
}
そしてそれは問題を修正しました:)
私は同様の問題を抱えていました。 lifecycle hooksドキュメント を見て、私はngAfterViewInit
をngAfterContentInit
に変更しました、そしてそれはうまくいきました。
このエラーはアプリケーションの実際の問題を示しているため、例外をスローすることは理にかなっています。
devMode
では、モデルが変更されたかどうかをチェックするために、定期的な変更検出の実行ごとに変更検出が追加の順番を追加します。
モデルが通常と追加の変更検出ターンの間に変更された場合、これは次のいずれかを示します。
これはどちらも悪いことです。なぜなら、モデルが決して安定化しない可能性があるからです。
モデルが安定するまでAngularが変更検出を実行すると、永久に実行される可能性があります。 Angularが変更検出を実行しない場合、ビューはモデルの現在の状態を反映していない可能性があります。
更新
OPの自己応答 firstから始めることを強くお勧めします。constructor
でできることとngOnChanges()
で何をするべきかについて正しく考えてください。
オリジナル
これは答えよりも補足的なメモですが、誰かに役立つかもしれません。ボタンの存在をフォームの状態に依存させようとしたときに、私はこの問題に遭遇しました。
<button *ngIf="form.pristine">Yo</button>
私の知る限りでは、この構文はボタンが条件に基づいてDOMに追加されたり削除されたりすることにつながります。これがExpressionChangedAfterItHasBeenCheckedError
につながります。
私の場合の修正(違いの完全な意味を理解することを私は主張しませんが)、代わりにdisplay: none
を使うことでした:
<button [style.display]="form.pristine ? 'inline' : 'none'">Yo</button>
私の場合は、テストの実行中にスペックファイルにこの問題がありました。
私はngIf
を [hidden]
に変更しなければなりませんでした
<app-loading *ngIf="isLoading"></app-loading>
から
<app-loading [hidden]="!isLoading"></app-loading>
以下の手順に従ってください。
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();
}
コンポーネント内の配列の1つで値が変化していたのと同じ問題に直面していました。しかし、値の変化による変化を検出する代わりに、コンポーネントの変化の検出方法をonPush
(値の変化ではなくオブジェクトの変化による変化を検出する)に変更しました。
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush
selector: -
......
})
面白い答えがありましたが、私のニーズに合うものは見つかりませんでした。最も近いのは@ chittrang-mishraで、これは1つの特定の関数のみを参照し、私のアプリのように複数のトグルを参照しません。
DOMの一部ではない[hidden]
を利用するために*ngIf
を使用したくなかったので、エラーを修正するのではなく抑制するので、次の解決策を見つけるのは最善ではないかもしれません。最終結果が正しいことを知っていれば、それは私のアプリでは問題ないようです。
AfterViewChecked
を実装し、constructor(private changeDetector : ChangeDetectorRef ) {}
を追加してから
ngAfterViewChecked(){
this.changeDetector.detectChanges();
}
多くの人が助けてくれたので、これが他の人に役立つことを願っています。
そのため、変更検出の背後にあるメカニズムは、実際には変更検出ダイジェストと検証ダイジェストの両方が同期して実行されるように機能します。つまり、プロパティを非同期に更新しても、検証ループが実行されているときに値は更新されず、ExpressionChanged...
エラーは発生しません。このエラーが発生するのは、検証プロセスの間、Angularが変化検出フェーズで記録した値とは異なる値を認識するためです。それを避けるために....
1)changeDetectorRefを使用する
2)setTimeOutを使用してください。これはあなたのコードを別のVMでマクロタスクとして実行します。 Angularは検証プロセス中にこれらの変更を確認しないため、そのエラーは発生しません。
setTimeout(() => {
this.isLoading = true;
});
3)あなたが本当に同じVMの上であなたのコードを実行したいならば、のように使います
Promise.resolve(null).then(() => this.isLoading = true);
これはマイクロタスクを作成します。マイクロタスクキューは、現在の同期コードの実行が完了した後に処理されるため、プロパティの更新は検証ステップの後に行われます。
Angular Runは検出を変更し、子コンポーネントに渡された値が変更されたことを検出するとAngularがエラーをスローしますExpressionChangedAfterItHasBeenCheckedError クリックして詳細
これを修正するために、AfterContentCheckedライフサイクルフックを使用できます。
import { ChangeDetectorRef, AfterContentChecked} from '@angular/core';
constructor(
private cdref: ChangeDetectorRef) { }
ngAfterContentChecked() {
this.cdref.detectChanges();
}
@HostBinding
は、このエラーの分かりにくい原因となる可能性があります。たとえば、コンポーネントに次のHostバインディングがあるとします。
// 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' };
}
これが起こるのです:
carousel
に割り当てられますngAfterViewInit()
まではcarousel
にアクセスできません(nullになります)style_groupBG = 'red'
を設定する設定を割り当てますbackground: red
が設定されます。carousel.style.background
に変更を見つけ、これが問題ではないことを知るほど賢くないので例外を投げます。1つの解決策は、ImageCarousel内に別のラッパーdivを導入してその上に背景色を設定することですが、HostBinding
を使用することによる利点の一部が得られません(親がオブジェクトの境界全体を制御できるなど)。
より良い解決策は、親コンポーネントで、設定後にdetectChanges()を追加することです。
ngAfterViewInit()
{
this.carousel.carouselConfig = { ... };
this.cdr.detectChanges();
}
これは非常に明白に見えるかもしれません、そして他の答えと非常によく似ていますが、微妙な違いがあります。
開発中の後半まで@HostBinding
を追加しない場合を考えてみましょう。突然このエラーに遭遇しても意味がありません。
私が*ngIf
を追加したとき、私の問題は明白でした、しかしそれは原因ではありませんでした。このエラーは、{{}}
タグ内のモデルを変更してから、後で*ngIf
ステートメントに変更後のモデルを表示しようとしたことが原因でした。これが例です:
<div>{{changeMyModelValue()}}</div> <!--don't do this! or you could get error: ExpressionChangedAfterItHasBeenCheckedError-->
....
<div *ngIf="true">{{myModel.value}}</div>
この問題を解決するために、changeMyModelValue()を呼び出した場所をより意味のある場所に変更しました。
私の状況では、子コンポーネントがデータを変更するたびにchangeMyModelValue()を呼び出す必要がありました。親がそれを処理できるように子コンポーネントでイベントを作成して発行する必要がありました(changeMyModelValue()を呼び出すことによって)。 https://angular.io/guide/component-interaction#parent-listens-for-childを参照してください。 -event
私はIonic3(このテクノロジスタックの一部としてAngular 4を使用しています)にこの種のエラーがありました。
私のためにそれはこれをやっていた:
<ion-icon [name]="getFavIconName()"></ion-icon>
そのため、画面が動作していたモードごとに、 ion-icon の型をpin
からremove-circle
に条件付きで変更しようとしていました。
代わりに* ngIFを追加する必要があると思います。
これが何が起こっているのかについての私の考えです。私はドキュメントを読んでいませんが、これがエラーが表示される理由の一部であると確信しています。
*ngIf="isProcessing()"
* ngIfを使用すると、条件が変わるたびに要素を追加または削除してDOMを物理的に変更します。そのため、ビューにレンダリングされる前に条件が変わると(Angularの世界では非常に可能性が高いです)、エラーがスローされます。開発モードとプロダクションモードの間の説明 here を参照してください。
[hidden]="isProcessing()"
[hidden]を使用する場合、DOMを物理的に変更するのではなく、単にビューから要素を隠すだけです。おそらくCSSを後ろ側で使用します。要素はまだDOMにありますが、条件の値によっては表示されません。そのため、[hidden]を使用してもエラーが発生しません。
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();
}
私の問題のために、私は github - "afterViewInitのコンポーネントの 'モデルでない'値を変更するときにExpressionChangedAfterItHasBeenCheckedError"を読んでいて、ngModelを追加することにしました
<input type="hidden" ngModel #clientName />
問題は解決しました。誰かに役立つことを願っています。
Component.tsで宣言されていないcomponent.htmlの変数を使用していたため、このエラーが発生しました。 HTMLでパーツを削除すると、このエラーはなくなりました。
モーダルでreduxアクションをディスパッチしており、その時点でモーダルが開かれていないため、このエラーが発生しました。モーダルコンポーネントが入力を受け取った瞬間にアクションをディスパッチしていました。そこで、モーダルが開かれ、アクションが配信されることを確認するために、そこにsetTimeoutを配置します。
解決策... servicesとrxjs ...イベント発行元とプロパティバインディングはどちらもrxjsを使用します。イベント発行元がrxjsを使用していることを忘れないでください。簡単に言うと、サービスを作成し、オブザーバブル内で、各コンポーネントにオブザーバをサブスクライブさせ、必要に応じて新しい値またはcosume値を渡します。
私の場合は、BehavioralSubject LoadingService
を持つisLoading
に非同期プロパティがありました。
[hidden] モデルを使用するとうまくいくが、 * ngIf が失敗する
<h1 [hidden]="!(loaderService.isLoading | async)">
THIS WORKS FINE
(Loading Data)
</h1>
<h1 *ngIf="!(loaderService.isLoading | async)">
THIS THROWS ERROR
(Loading Data)
</h1>
サブスクライブスコープにコードブロックがありましたが、それを上位スコープに変更しただけで正常に動作します。
このエラーはかなり混乱を招く可能性があるので、発生した時点について誤った仮定をするのは簡単です。影響を受けるコンポーネント全体に適切な場所にこのような多くのデバッグステートメントを追加すると便利です。これは流れを理解するのに役立ちます。
このような親の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');
Child/services/timerコールバックで:
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つのことは、不明瞭な色の値に設定することで、自分が思っているものをデバッグしていることを確認します。
これが誰かがここに来るのを助けることを願っています:次の方法で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>