チェックされた後、式___が変更されました


286

この単純なプランクのコンポーネントはなぜですか

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App {
  message:string = 'loading :(';

  ngAfterViewInit() {
    this.updateMessage();
  }

  updateMessage(){
    this.message = 'all done loading :)'
  }
}

投げ:

例外:式 'I'm {{message}} in App @ 0:5'は確認後に変更されました。以前の値: 'I'm loading :('。現在の値: 'I'm all done loading :)' in [I'm {{message}} in App @ 0:5]

ビューが開始されたときに単純なバインディングを更新しているだけですか?


7
エラーについて知っておく必要があるすべてExpressionChangedAfterItHasBeenCheckedErrorの記事で、動作が詳細に説明されています。
Max Koretskyi 2017

stackoverflow.com/questions/39787038/…ChangeDetectionStrategyを使用する場合は変更することを検討してくださいdetectChanges()
Luis Limas

入力コントロールがあり、メソッドにデータを入力していて、同じメソッドでそれに値を割り当てていると考えてください。コンパイラーは、新しい値/以前の値と混同されます。そのため、バインドと入力は異なる方法で行う必要があります。
MAC

回答:


292

drewmooreが述べたように、この場合の適切な解決策は、現在のコンポーネントの変更検出を手動でトリガーすることです。これはdetectChanges()ChangeDetectorRef(からインポートされたangular2/core)オブジェクトのメソッド、またはそのmarkForCheck()メソッドを使用して行われ、親コンポーネントも更新されます。関連する

import { Component, ChangeDetectorRef, AfterViewInit } from 'angular2/core'

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App implements AfterViewInit {
  message: string = 'loading :(';

  constructor(private cdr: ChangeDetectorRef) {}

  ngAfterViewInit() {
    this.message = 'all done loading :)'
    this.cdr.detectChanges();
  }

}

また、念のためにngOnInitsetTimeout、およびenableProdModeアプローチを示すPlunkersもここにあります。


7
私の場合、モーダルを開いていました。モーダルを開いた後、「式___がチェックされた後に変更されました」というメッセージが表示されたため、私のソリューションにthis.cdr.detectChanges();が追加されました。私のモーダルを開いた後。ありがとう!
ホルヘカサリエゴ2016

cdrプロパティの宣言はどこにありますか?宣言のcdr : any下にそのような線が見えると思いmessageます。私は何かを逃した心配だけですか?
CodeCabbie 2017年

1
@CodeCabbieコンストラクタの引数にあります。
スラスキー

1
この解決策は私の問題を解決します!どうもありがとう!非常に明確でシンプルな方法。
lwozniak 2017年

3
これは私に役立ちました-いくつかの変更を加えました。ngForループを介して生成されたli要素をスタイルする必要がありました。並べ替えパイプのパラメーターとして使用されているブール値を更新する「並べ替え」をクリックすると、innerTextに基づいてリストアイテム内のスパンの色を変更する必要がありました(並べ替えられた結果はデー​​タのコピーだったため、スタイルが取得されませんでした) ngStyleのみで更新されます)。-代わりに「AfterViewInit」を使用しての、私が使用し「AfterViewChecked」を -私はも確認しましたインポートおよび実装 AfterViewCheckedを。 注:パイプを「pure:false」に設定しても機能しなかったため、この追加の手順を追加する必要がありました(:
Chloe Corrigan '20

170

まず、この例外は、アプリを開発モードで実行している場合にのみスローされることに注意してください(ベータ-0以降のデフォルトです):enableProdMode()アプリをブートストラップするときに呼び出しても、スローされません(を参照)更新されたプランク)。

第二に、それをしないでくださいこの例外は正当な理由でスローされため、これを、開発モードでは、変更検出のすべてのラウンドの直後に、最初のラウンドの終了以降にバインディングが変更されていないことを確認する2番目のラウンドが続きます。これは、変更が変更検出自体によって引き起こされていることを示します。

プランクでは、最初の変更検出ターンの一部として発生するフックで発生{{message}}するへの呼び出しによってバインディングが変更されます。それ自体は問題ありませんが、問題はsetMessage()ngAfterViewInitsetMessage()バインディング変更しますが、新しいラウンドの変更検出をトリガーしないことです。つまり、今後のラウンドの変更検出がどこかでトリガーされるまで、この変更は検出されません。

要点:バインディングを変更するものはすべて、変更時に一連の変更検出をトリガーする必要があります。

これを行う方法の例についてのすべての要求に応じて更新します。@ MarkRajcokが指摘した回答の 3つのメソッドと同様に、@ Tychoのソリューションが機能します。しかし率直に言って、ng1に頼っていたハックのように、それらはすべて私にとって醜く間違っていると感じています。

確かに、これらのハッキングが適切である状況は時々ありますが、ごくまれにしか使用していない場合は、フレームワークとの反応性を完全に受け入れるのではなく、フレームワークと戦っていることを示しています。

IMHO、これにアプローチするより慣用的な「Angular2の方法」は、次のようなものです。(plunk

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message | async}} </div>`
})
export class App {
  message:Subject<string> = new BehaviorSubject('loading :(');

  ngAfterViewInit() {
    this.message.next('all done loading :)')
  }
}

13
setMessage()が変更検出の新しいラウンドをトリガーしないのはなぜですか?Angular 2は、UIで何かの値を変更すると、自動的に変更検出をトリガーすると思いました。
Danny Libin、

4
@drewmoore「バインディングを変更するものはすべて、変更検出のラウンドをトリガーする必要があります」。どうやって?それは良い習慣ですか?すべてを1回の実行で完了する必要はありませんか?
Daniel Birowsky Popeski

2
@Tycho、確かに。私はそのコメントを書いたので、私は私が説明する別の質問答えてきた変化検出を実行するための3つの方法を、含まれてdetectChanges()
Mark Rajcok、2016

4
現在の質問の本文では、呼び出されたメソッドはではupdateMessageなくと名付けられていることに注意してくださいsetMessage
superjos

3
@Daynil、質問の下でコメントで与えられたブログを読むまで、私は同じ気持ちを持っていました:blog.angularindepth.com/… これは、これを手動で行う必要がある理由を説明しています。この場合、angularの変更検出にはライフサイクルがあります。これらのライフサイクルの間に何かが値を変更している場合は、強力な変更検出を実行する必要があります(またはsettimeout-次のイベントループで実行され、変更検出を再度トリガーします)。
Mahesh 2017年

50

ngAfterViewChecked() 私のために働いた:

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

constructor(private cdr: ChangeDetectorRef) { }
ngAfterViewChecked(){
   //your code to update the model
   this.cdr.detectChanges();
}

描いたように、2つのフェーズからなる角度の変更検出サイクルは、子のビューが変更された後に変更を検出する必要があります。これを行う最善の方法は、角度自体によって提供されるライフサイクルフックを使用して、手動で角度を要求することです変更を検出してバインドします。私の個人的な見解では、これは適切な答えのようです。
Joey587

1
これは、階層動的コンポーネントを一緒にロードするための私にとっては機能します。
Mehdi Daustany

47

角度コアからChangeDetectionStrategyを追加することでこれを修正しました。

import {  Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'page1',
  templateUrl: 'page1.html',
})

これでうまくいきました。私はこれと使用ChangeDetectorRefの違いは何だろう
アリ

1
うーん...その後、変更検出器のモードは最初にCheckOnceドキュメント)に設定されます
Alex Klaus

はい、エラー/警告はなくなりました。しかし、それは非常に大きい5-7秒の違いのように、以前よりもはるかに多くの読み込み時間を費やしています。
Kapil Raghuwanshi

1
@KapilRaghuwanshi this.cdr.detectChanges();ロードしようとしているものをすべて実行します。変更の検出がトリガーされていないことが原因である可能性がある
Harshal Carpenter

36

ngOnInitメンバー変数を変更するだけなので使用できませんmessageか?

子コンポーネントへの参照にアクセスする場合は@ViewChild(ChildComponent)、実際にそれを待つ必要がありますngAfterViewInit

ダーティな修正はupdateMessage()、たとえばsetTimeoutを使用して次のイベントループでを呼び出すことです。

ngAfterViewInit() {
  setTimeout(() => {
    this.updateMessage();
  }, 1);
}

4
コードをngOnInitメソッドに変更するとうまくいきました。
Faliorn

29

これについて私は上記の答えを試しましたが、Angularの最新バージョン(6以降)では多くが機能しません

私は最初のバインディングが行われた後に変更が必要なマテリアルコントロールを使用しています。

    export class AbcClass implements OnInit, AfterContentChecked{
        constructor(private ref: ChangeDetectorRef) {}
        ngOnInit(){
            // your tasks
        }
        ngAfterContentChecked() {
            this.ref.detectChanges();
        }
    }

私の答えを追加すると、これは特定の問題を解決するのに役立ちます。


これは実際には私の場合には機能しましたが、タイプミスがあります。AfterContentCheckedを実装した後は、ngAfterViewInitではなくngAfterContentCheckedを呼び出す必要があります。
Tomas Lukac

私は現在バージョン8.2.0を使用しています:)
Tomas

1
@TomášLukáč返信ありがとう、答えを更新しました(y)
MarmiK

1
上記のいずれも機能しない場合は、この回答をお勧めします。
Amir Choubani

afterContentCheckedは、DOMが再計算または再チェックされるたびに呼び出されます。Angularバージョン9からの
親密さ

24

エラーについて知っておく必要があるすべてExpressionChangedAfterItHasBeenCheckedErrorの記事で、動作が詳細に説明されています。

セットアップの問題ngAfterViewInitは、変更検出がDOM更新を処理した後にライフサイクルフックが実行されることです。また、このフックのテンプレートで使用されているプロパティを効果的に変更しているため、DOMを再レンダリングする必要があります。

  ngAfterViewInit() {
    this.message = 'all done loading :)'; // needs to be rendered the DOM
  }

これには別の変更検出サイクルが必要であり、Angularの設計では1つのダイジェストサイクルのみが実行されます。

基本的に、それを修正する方法は2つあります。

  • 非同期プロパティを更新のいずれかを使用してsetTimeoutPromise.thenまたはテンプレートで参照非同期観測可能

  • DOM更新の前にフックでプロパティ更新を実行します-ngOnInit、ngDoCheck、ngAfterContentInit、ngAfterContentChecked。


あなたの記事を読んでください:blog.angularindepth.com/…、もう1つblog.angularindepth.com/…をもうすぐ読むでしょう。それでもこの問題の解決策を見つけることができませんでした。ngDoCheckまたはngAfterContentCheckedライフサイクルフックを追加してthis.cdr.markForCheck();を追加するとどうなるか教えてください。(ChangeDetectorRefのcdr)その中に。ライフサイクルフックとその後のチェックが完了した後で変更をチェックするのは正しい方法ではありませんか?
Harsimer

9

適切なライフサイクルフックでメッセージを更新する必要があります。この場合はngAfterContentCheckedngAfterViewInitngAfterViewInitで変数メッセージのチェックが開始されましたが、まだ終了していないため。

参照:https : //angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#afterview

したがって、コードは次のようになります。

import { Component } from 'angular2/core'

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App {
  message: string = 'loading :(';

  ngAfterContentChecked() {
     this.message = 'all done loading :)'
  }      
}

参照作業のデモ Plunker上を。


私は、Observableによって作成された@ViewChildren()長さベースのカウンターの組み合わせを使用していました。これが私にとって有効な唯一の解決策でした!
msanford 2017年

2
上記ngAfterContentCheckedとの組み合わせがChangeDetectorRefうまくいきました。オンngAfterContentChecked-と呼ばthis.cdr.detectChanges();
クナルDethe

7

初期化された直後に既存の値が更新されるため、このエラーが発生します。したがって、既存の値がDOMでレンダリングされた後に新しい値を更新する場合、それは正常に機能します。

例えばあなたは使うことができます

ngOnInit() {
    setTimeout(() => {
      //code for your new value.
    });

}

または

ngAfterViewInit() {
  this.paginator.page
      .pipe(
          startWith(null),
          delay(0),
          tap(() => this.dataSource.loadLessons(...))
      ).subscribe();
}

ご覧のとおり、setTimeoutメソッドで時間について言及していません。これはJavaScript APIではなくブラウザ提供のAPIであるため、ブラウザスタックで個別に実行され、コールスタックアイテムが完了するまで待機します。

どのようにブラウザーAPIがコンセプトを呼び起こすかは、Youtubeのビデオの1つでフィリップロバーツによって説明されています(ハックとはイベントループとは何ですか?)。


4

ngOnInt()-メソッドにupdateMessage()への呼び出しを置くこともできます。少なくともそれは私にとっては機能します

ngOnInit() {
    this.updateMessage();
}

RC1では、これは例外をトリガーしません


3

rxjs Observable.timer関数を使用してタイマーを作成し、サブスクリプションのメッセージを更新することもできます。                    

Observable.timer(1).subscribe(()=> this.updateMessage());

2

ngAfterViewInit()が呼び出されるとコードが更新されるため、エラーがスローされます。ngAfterViewInitが行われたときに初期値が変更されたことを意味します。それをngAfterContentInit()で呼び出した場合、エラーはスローされません。

ngAfterContentInit() {
    this.updateMessage();
}

0

私は十分な評判がないので@Biranchiの投稿にコメントできませんでしたが、問題は解決しました。

注意すべき1つのこと!コンポーネントにchangeDetection:ChangeDetectionStrategy.OnPushを追加しても機能せず、その子コンポーネント(ダムコンポーネント)を親にも追加してみてください。

これでバグは修正されましたが、これの副作用は何でしょうか。


0

データテーブルでの作業中に同様のエラーが発生しました。* ngForを別の* ngForデータテーブル内で使用すると、角度変更サイクルが干渉するため、このエラーがスローされます。SOデータテーブル内でデータテーブルを使用する代わりに、1つの通常のテーブルを使用するか、mf.dataを配列名に置き換えます。これは正常に動作します。


0

私は最も簡単な解決策は次のようになると思います:

  1. 関数またはセッターを介して、変数に値を割り当てる実装を1つ作成します。
  2. (static working: boolean)この関数が存在するクラスにクラス変数を作成し、関数を呼び出すたびに、好きなだけtrueにします。関数内で、workingの値がtrueの場合は、何もせずにすぐに戻ります。それ以外の場合は、必要なタスクを実行します。タスクが完了したら、つまり、コードの行の最後で、または値の割り当てが完了したらsubscribeメソッド内で、この変数を必ずfalseに変更してください。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.