web-dev-qa-db-ja.com

ExpressionChangedAfterItHasBeenCheckedError:式はチェック後に変更されました。前の値: 'undefined'

私はすでにスタックオーバーフローに投稿された同じ質問がたくさんあり、ランタイムエラーを回避するためにさまざまな解決策を試しましたが、それらのどれも私のために働いていません。

Error

コンポーネントとHTMLコード

export class TestComponent implements OnInit, AfterContentChecked {
    @Input() DataContext: any;
    @Input() Position: any;
    sampleViewModel: ISampleViewModel = { DataContext: : null, Position: null };
    constructor(private validationService: IValidationService, private modalService: NgbModal, private cdRef: ChangeDetectorRef) {
    }

    ngOnInit() {

    }
    ngAfterContentChecked() {

            debugger;
            this.sampleViewModel.DataContext = this.DataContext;
            this.sampleViewModel.Position = this.Position;

    }


<div class="container-fluid sample-wrapper text-center" [ngClass]="sampleViewModel.DataContext?.Style?.CustomCssClass +' samplewidget-'+ sampleViewModel.Position?.Columns + 'x' + sampleViewModel.Position?.Rows">
     //some other html here
</div>

注意:このコンポーネントはDynamicComponentLoaderを使用して動的にロードされます

トラブルシューティングの後、いくつかの問題を特定しました

まず、この子コンポーネントは、DynamicComponentResolverを使用して以下のような入力値を渡すことで動的にロードされます

 ngAfterViewInit() {
    this.renderWidgetInsideWidgetContainer();

  }


  renderWidgetInsideWidgetContainer() {
    let component = this.storeFactory.getWidgetComponent(this.dataSource.ComponentName);
    let componentFactory = this._componentFactoryResolver.resolveComponentFactory(component);
    let viewContainerRef = this.widgetHost.viewContainerRef;
    viewContainerRef.clear();
    let componentRef = viewContainerRef.createComponent(componentFactory);
    debugger;
    (<IDataBind>componentRef.instance).WidgetDataContext = this.dataSource.DataContext;
    (<IDataBind>componentRef.instance).WidgetPosition = this.dataSource.Position;

  }

子コンポーネントのhtmlを次のように変更しても、同じエラーが発生します。angular ngclass属性を追加するだけです

<div class="container-fluid ds-iconwidget-wrapper text-center" [ngClass]="Sample">

</div>

データバインディングとすべてが正常に機能していますが、親コンポーネントで何かする必要がありますか?私はすでに子コンポーネントのすべてのライフサイクルイベントを試しました

42
Code-EZ

ngAfterContentCheckedライフサイクルフックは、子コンポーネント/ディレクティブのバインディングの更新が既に完了しているときにトリガーされます。ただし、ngClassディレクティブのバインディング入力として使用されるプロパティを更新しています。それは問題。 Angularが検証ステージを実行すると、プロパティへの保留中の更新があることを検出し、エラーをスローします。

エラーをよりよく理解するには、次の2つの記事を読んでください。

ngAfterViewInitライフサイクルフックでプロパティを変更する必要がある理由を考えてください。 ngAfterViewInit/Checkedの前にトリガーされる他のライフサイクル、たとえばngOnInitまたはngDoCheckまたはngAfterContentCheckedは機能します。

それを修正するには、renderWidgetInsideWidgetContainerngOnInit()ライフサイクルフックに移動します。

angularの後にコンテンツを更新したことをngAfterContentCheckedに伝える必要があります。ChangeDetectorRef@angular/coreからインポートしてdetectChangesを呼び出すことができます

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

constructor( private cdref: ChangeDetectorRef ) {}


ngAfterContentChecked() {

this.sampleViewModel.DataContext = this.DataContext;
this.sampleViewModel.Position = this.Position;
this.cdref.detectChanges();

 }
32

<ng-content>*ngIfを使用している場合、このループに陥ることになります。

私が見つけた唯一の方法は、*ngIfdisplay:none機能に変更することでした

1
bresleveloper

私はあなたと同じことをしようと同じ問題を抱えていたので、リッチー・フレディソンの答えに似た何かでそれを修正しました。

CreateComponent()を実行すると、未定義の入力変数で作成されます。その後、それらの入力変数にデータを割り当てると、物事が変わり、子テンプレートでそのエラーが発生します(私の場合は、入力データを割り当てた後に変更されたngIfで入力を使用していたためです)。

この特定のケースで回避できる唯一の方法は、データを割り当てた後に強制的に変更を検出することですが、ngAfterContentChecked()でそれをしませんでした。

あなたのサンプルコードを追うのは少し難しいですが、私の解決策があなたのために働くなら、それはこのようなものです(親コンポーネントで):

export class ParentComponent implements AfterViewInit {
  // I'm assuming you have a WidgetDirective.
  @ViewChild(WidgetDirective) widgetHost: WidgetDirective;

  constructor(
    private componentFactoryResolver: ComponentFactoryResolver,
    private changeDetector: ChangeDetectorRef
  ) {}

  ngAfterViewInit() {
    renderWidgetInsideWidgetContainer();
  }

  renderWidgetInsideWidgetContainer() {
    let component = this.storeFactory.getWidgetComponent(this.dataSource.ComponentName);
    let componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
    let viewContainerRef = this.widgetHost.viewContainerRef;
    viewContainerRef.clear();
    let componentRef = viewContainerRef.createComponent(componentFactory);
    debugger;
    // This <IDataBind> type you are using here needs to be changed to be the component
    // type you used for the call to resolveComponentFactory() above (if it isn't already).
    // It tells it that this component instance if of that type and then it knows
    // that WidgetDataContext and WidgetPosition are @Inputs for it.
    (<IDataBind>componentRef.instance).WidgetDataContext = this.dataSource.DataContext;
    (<IDataBind>componentRef.instance).WidgetPosition = this.dataSource.Position;
    this.changeDetector.detectChanges();
  }
}

私は複数のHost要素を持っているので、@ ViewChildの代わりに@ViewChildrenを使用していることを除いて、ほとんど同じです。

1
rooby

2つのソリューション:

  1. バインド変数がある場合は確認してから、そのコードをsettimeout({}、0);に移動します。
  2. 関連するコードをngAfterViewInitメソッドに移動します
1
Chetan Laddha
setTimeout(() => { // your code here }, 0);

コードをsetTimeoutでラップし、動作しました

1
Andris
 ngAfterViewInit() {
   setTimeout(() => {
     this.renderWidgetInsideWidgetContainer();
   }, 0);
  }

これは、この問題を解決するための優れたソリューションです。