ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后发生了变化。以前的价值:' undefined'

时间:2017-08-02 17:56:46

标签: angular runtime-error

我知道堆栈溢出中已经发布了很多相同的问题并尝试了不同的解决方案以避免运行时错误,但它们都没有为我工作。

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,如下所示,我也得到同样的错误。只需添加一个角度ngcl​​ass属性

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

</div>

我的数据绑定,一切正常。我需要对父组件做什么吗? 我已经尝试了子组件中的所有生命周期事件

11 个答案:

答案 0 :(得分:24)

您必须告诉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();

 }

答案 1 :(得分:19)

当子组件/指令的绑定更新已经完成时,将触发ngAfterContentChecked生命周期钩子。但是,您正在更新用作ngClass指令的绑定输入的属性。那就是问题所在。当Angular运行验证阶段时,它会检测到属性的挂起更新并引发错误。

要更好地理解错误,请阅读以下两篇文章:

考虑一下为什么需要在ngAfterViewInit生命周期钩子中更改属性。在ngAfterViewInit/Checked之前触发的任何其他生命周期都可以使用,例如ngOnInitngDoCheckngAfterContentChecked

因此,要解决此问题,请将renderWidgetInsideWidgetContainer移至ngOnInit()生命周期挂钩。

答案 2 :(得分:3)

* NgIf可能在这里造成问题,因此要么不显示任何CSS,要么更简单的方法是使用[hidden] =“!condition”

答案 3 :(得分:2)

我遇到麻烦了。

错误:ExpressionChangedAfterItHasBeenCheckedError:检查表达式后,表达式已更改。 “ mat-checkbox-checked”的先前值:“ true”。当前值:“ false”。

这里的问题是,直到下一个更改检测周期运行,才检测到更新的值。

最简单的解决方案是添加变更检测策略。 将这些行添加到您的代码中:

import { ChangeDetectionStrategy } from "@angular/core";  // import

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: "abc",
  templateUrl: "./abc.html",
  styleUrls: ["./abc.css"],
})

答案 4 :(得分:1)

两种解决方案:

  1. 请确保您有一些绑定变量,然后将该代码移至 settimeout ({},0);
  2. 将您的相关代码移到 ngAfterViewInit 方法

答案 5 :(得分:1)

这段代码解决了我的问题。

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

constructor( private cdref: ChangeDetectorRef ) {}

ngAfterContentChecked() {
  this.cdref.detectChanges();
}

答案 6 :(得分:0)

我在尝试做与您相同的事情时遇到了同样的问题,并用类似于里奇·弗雷迪森(Richie Fredicson)的答案来解决它。

运行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();
  }
}

除了我使用@ViewChildren而不是@ViewChild之外,我的矿井与其他矿井几乎相同,因为我有多个宿主元素。

答案 7 :(得分:0)

如果您将<ng-content>*ngIf结合使用,则肯定会陷入此循环。

我发现的唯一出路是将*ngIf更改为display:none功能

答案 8 :(得分:0)

setTimeout(() => { // your code here }, 0);

我将代码包装在setTimeout中,并且有效

答案 9 :(得分:0)

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

这是解决此问题的好方法。

答案 10 :(得分:0)

尝试此操作,以在 ngOnInit()

中调用您的代码
someMethod() // emitted method call from output
{
    // Your code 
}

ngOnInit(){
  someMethod(); // call here your error will be gone
}