web-dev-qa-db-ja.com

コンポーネントプロパティが現在の日時に依存する場合のAngular 2「式がチェックされた後に変更された」の例外を管理する方法

私のコンポーネントは現在の日時に依存するスタイルを持っています。私のコンポーネントには、次のような機能があります。

  private fontColor( dto : Dto ) : string {
    // date d'exécution du dto
    let dtoDate : Date = new Date( dto.LastExecution );

    (...)

    let color =  "hsl( " + hue + ", 80%, " + (maxLigness - lightnessAmp) + "%)";

    return color;
  }

lightnessAmpは現在の日時から計算されます。 dtoDateが過去24時間以内の場合、色が変わります。

正確なエラーは次のとおりです。

確認後に式が変更されました。以前の値: 'hsl(123、80%、49%)'現在の値: 'hsl(123、80%、48%)'

例外が開発モードで表示されるのは、値がチェックされた瞬間にだけです。チェックされた値が更新された値と異なる場合は、例外がスローされます。

そのため、例外を防ぐために、次のフックメソッドで各ライフサイクルで現在の日時を更新しようとしました。

  ngAfterViewChecked()
  {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
  }

...しかし成功しなかった。

106

変更後に変更検出を明示的に実行します。

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

constructor(private cdRef:ChangeDetectorRef) {}

ngAfterViewChecked()
{
  console.log( "! changement de la date du composant !" );
  this.dateNow = new Date();
  this.cdRef.detectChanges();
}
251

これはこのエラーを理解するための素晴らしい記事です。これは読むには長すぎません。

34
tomonari_t

@leocaseiroが github issue で述べたように。

私は簡単な修正を探している人のための3つの解決策を見つけました。

1)ngAfterViewInitからngAfterContentInitへの移行

2)#14748で提案されているようにngAfterViewCheckedと組み合わせてChangeDetectorRefに移動

3)ngOnInit()を使い続けますが、変更後はChangeDetectorRef.detectChanges()を呼び出します。

18
candidJ

この問題に対する小さな回避策:

ngAfterViewInit() { // or ngOnInit or whatever
    setTimeout(() => {
        this.dateNow = new Date();
    });
}
15
Sergei Panfilov

今回の場合は、changeDetectionをコンポーネントに追加してngAfterContentCheckedでdetectChanges()を呼び出すことで修正しました。次のようにコーディングします。

@Component({
  selector: 'app-spinner',
  templateUrl: './spinner.component.html',
  styleUrls: ['./spinner.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SpinnerComponent implements OnInit, OnDestroy, AfterContentChecked {

  show = false;

  private subscription: Subscription;

  constructor(private spinnerService: SpinnerService, private changeDedectionRef: ChangeDetectorRef) { }

  ngOnInit() {
    this.subscription = this.spinnerService.spinnerState
      .subscribe((state: SpinnerState) => {
        this.show = state.show;
      });
  }

  ngAfterContentChecked(): void {
      this.changeDedectionRef.detectChanges();
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

}

私が何度も使ったことのある小さな作品

Promise.resolve(null).then(() => {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
    this.cdRef.detectChanges();
});

私は主に "null"をコントローラで使用する変数に置き換えます。

3
Khateeb321

これは、このエラーの原因についてのtomonari_t回答 からの抜粋です。私がこれを理解するのを助けた部分。

記事全文は、ここに示されているすべての点に関する実際のコード例を示しています。

根本的な原因は角度のあるライフサイクルです。

各操作の後Angularは、操作の実行に使用した値を記憶しています。それらはコンポーネントビューのoldValuesプロパティに格納されます。

すべてのコンポーネントAngularのチェックが完了したら、次のダイジェストサイクルを開始しますが、操作を実行する代わりに、現在の値を前回のダイジェストサイクルで記憶した値と比較します。

次の操作はダイジェストサイクルでチェックされています。

子コンポーネントに渡される値が、現在これらのコンポーネントのプロパティを更新するために使用される値と同じであることを確認してください。

dOM要素の更新に使用される値が、これらの要素の更新に使用される値と同じであることを確認してください。

すべての子コンポーネントをチェックします

そのため、比較した値が異なるとエラーがスローされます。、Blogger Max Koretskyi は次のように述べています。

原因は常に子コンポーネントまたはディレクティブです。

そして最後にここに通常このエラーを引き起こすいくつかの現実世界のサンプルがあります:

  • 共有サービス
  • 同期イベント放送
  • 動的コンポーネントのインスタンス化

すべてのサンプルはここで見つけることができます ここで (plunkr)、問題は動的コンポーネントのインスタンス化でした。

また、私自身の経験から、私はすべての人がsetTimeout解決法を避けることを強くお勧めします。私の場合は、「ほぼ」無限ループ(21回の呼び出し)。

他のコンポーネントの値を変更するたびに影響を受ける可能性があるので、Angularライフサイクルを常に念頭に置いておくことをお勧めします。このエラーでAngularはあなたに言っています:

あなたは多分これを間違ったやり方でやっているでしょう、あなたはあなたが正しいと確信していますか?

同じブログでもこう言われています。

多くの場合、修正は正しい変更検出フックを使用して動的コンポーネントを作成することです。

私のための簡単なガイドは、コーディング中に少なくとも次の2つのことを考慮することです(私は時間をかけてそれを補完しようとします):

  1. 代わりに、子コンポーネントから親コンポーネントの値を変更しないでください。親から値を変更してください。
  2. @Inputおよび@Outputディレクティブを使用するときは、コンポーネントが完全に初期化されていない限り、ライフサイクルの変更を引き起こさないようにしてください。
2
Luis Limas

私はあなたが想像することができる最善かつ最もきれいな解決策だと思います:

@Component( {
  selector: 'app-my-component',
  template: `<p>{{ myData?.anyfield }}</p>`,
  styles: [ '' ]
} )
export class MyComponent implements OnInit {
  private myData;

  constructor( private myService: MyService ) { }

  ngOnInit( ) {
    /* 
      async .. await 
      clears the ExpressionChangedAfterItHasBeenCheckedError exception.
    */
    this.myService.myObservable.subscribe(
      async (data) => { this.myData = await data }
    );
  }
}

Angular 5.2.9でテスト済み

2
JavierFuentes