web-dev-qa-db-ja.com

式___はチェック後に変更されました

なぜこのコンポーネントは単純な plunk なのか

@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 :)'
  }
}

投げ:

例外:App @ 0:5の表現 'I'm {{message}}は、確認後に変更されました。以前の値: '私は読み込み中です:('。現在の値: '私はすべて読み込みを完了しました:)' ​​[App @ 0:5の{{{message}}]

私の見解が開始されたときに私がしているのが単純なバインディングを更新することだけなのですか?

201
drewmoore

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

2つ目は、これはしないでください正当な理由でこの例外がスローされているためです。要するに、devモードでは、毎回の変更検出の直後に2回目のラウンドが続きます。これは、変更が変更検出自体によって引き起こされていることを示しているためです。

あなたのplunkでは、バインディング{{message}}setMessage()への呼び出しによって変更されます。これはngAfterViewInitフックで起こります。これは最初の変化検出ターンの一部として起こります。それ自体は問題ありません - 問題はsetMessage()がバインディングを変更しますが、新しいラウンドの変更検出を引き起こさないということです。つまり、将来の変更ラウンドが別の場所で引き起こされるまでこの変更は検出されません。

要点:バインディングを変更したものはすべて、変更検出ラウンドを実行する必要があります。

その答えの例については、すべての要求に応えて更新してください。:@ Tychoの解決法は、 the answer @MarkRajcokで指摘されている3つの方法と同様です。 ng1で慣れ親しんだようなハックのように、私にとっては悪いことです。

確かに、これらのハックが適切である臨時状況があります、しかしもしあなたが非常に時折以外の何かでそれらを使っているなら、それはあなたが戦っている兆候ですその反応性を完全に受け入れるのではなく、フレームワークです。

私見、これに近づくより慣用的な、 "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 :)')
  }
}
133
drewmoore

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

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

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

  constructor(private cdr: ChangeDetectorRef) {}

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

}

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

211
Tycho

Angular CoreからChangeDetectionStrategyを追加することでこれを修正しました。

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

メンバー変数ngOnInitを変更しただけなので、messageを使用できませんか。

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

汚い解決策は次のイベントループでupdateMessage()を呼び出すことです。 setTimeout.

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

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

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

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

記事 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)の前に、フックでプロパティの更新を実行します。

NgAfterViewInitでは変数messageのチェックは開始されていますがまだ終了していないので、正しいライフサイクルフックでメッセージを更新するだけです。この場合はngAfterContentCheckedではなくngAfterViewInitです。

参照してください: 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の 実用的なデモ を参照してください。

6
RedPelle

このため、私は上記の答えを試してみましたが、Angularの最新バージョン(6以降)では多くのものが動作しません。

最初の製本後に変更を必要とする材料管理を使用しています。

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

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

5
MarmiK

NgOnInt() - メソッドにupdateMessage()への呼び出しを入れることもできます。少なくともそれは私にとってはうまくいきます。

ngOnInit() {
    this.updateMessage();
}

RC1では、これは例外を引き起こさない

4
Tobias Gassmann

ngAfterViewInit() が呼び出されるとコードが更新されるため、エラーが発生します。 ngAfterViewInitが実行されたときに初期値が変更されたことを意味します。 ngAfterContentInit() でそれを呼び出しても、エラーにはなりません。

ngAfterContentInit() {
    this.updateMessage();
}

Rxjs Observable.timer関数を使用してタイマーを作成してから、購読内のメッセージを更新することもできます。

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

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

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

これはバグを修正しました、しかし私はこれの副作用が何であるか疑問に思います。

0
TKDev