ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后已更改。上一个值:'undefined'


80

我知道在堆栈溢出中已经发布了很多相同的问题,并尝试了不同的解决方案来避免运行时错误,但是没有一个对我有用。

错误

组件和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>

我的数据绑定和一切工作正常。我是否需要对父组件执行任何操作?我已经尝试了子组件中的所有生命周期事件


你怎么添加TestComponent
Max Koretskyi

1
首先this.renderWidgetInsideWidgetContainer();ngAfterViewInit移至ngOnInit
yurzui

1
@Maximus感谢您的时间。.您的文章很棒
Code-EZ

1
@JEMI,不客气)我有很多有趣的文章,请查看
Max Koretskyi

1
@Maximus当然。我已经是你的追随者了。您的每篇文章都非常深入。
Code-EZ

Answers:


48

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

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

考虑一下为什么需要更改ngAfterViewInit生命周期挂钩中的属性。之前被触发任何其他生命周期ngAfterViewInit/Checked会工作,例如ngOnInitngDoCheckngAfterContentChecked

因此,要修复它移动renderWidgetInsideWidgetContainerngOnInit()生命周期挂钩。


为什么要更新中的属性ngAfterViewInit/Checked
Max Koretskyi

实际上,它没有更新,我将输入数据设置为一个视图模型,最后所有属性都像包装类一样通过此模型进行绑定。当我直接绑定@Input datacontext时,出现了同样的错误。没有解决方法可以通过任何生命周期事件来消除此错误。让我尝试您的答案中提到的ngAfterViewInit / Checked
Code-EZ

2
尝试ngOnInitngDoCheckngAfterContentChecked.如果仍然无法使用,请创建一个插塞
Max Koretskyi

嗯..根据我的理解,这很奇怪,这对我来说很奇怪。我怀疑这是因为动态组件加载程序。让我尝试创建一个punker。
Code-EZ

我已经尝试了所有生命周期事件,但不幸的是我无法解决此问题。但问题解决后,我的问题会通过添加更多积分来更新。希望这可以帮助您获得一些想法。
Code-EZ

100

你要告诉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();

 }

看到有问题的评论,我已经尝试过此解决方案,但仍然遇到相同的错误
Code-EZ

7
我导入了AfterContentChecked,它只有this.cdref.detectChanges(); 这对我
有用,

@MohamedAboElmagd您能发布如何使它工作吗?
Haseeb '18

1
使用this.cdref.detectChanges(); 经过任何修改
Mohamed Abo Elmagd '18

当我打开下拉菜单和咆哮项目时,它的调用时间可能是5月。
Asif vora

11

如果与您一起使用<ng-content>*ngIf势必会陷入此循环。

唯一的出路,我发现是改变*ngIfdisplay:none功能


或可以使用[hidden] =“!condition”
Pragati Dugar


4

两种解决方案:

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

这取决于将代码移至事件队列的时间,并且在当前执行完成时将执行该代码。JavaScript是一种单线程脚本语言,因此它可以一次执行一段代码(由于其单线程特性),这些代码块中的每一个都“阻塞”了其他异步事件的进程。仅当您当前的代码完成执行时,才会执行settimeout代码。
Chetan Laddha '19


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

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


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"],
})

1

我在尝试执行与您相同的操作时遇到了相同的问题,并使用类似于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之外,我的矿山与其他矿山几乎相同,因为我有多个宿主元素。


0

试试这个,在ngOnInit()中调用您的代码

someMethod() // emitted method call from output
{
    // Your code 
}

ngOnInit(){
  someMethod(); // call here your error will be gone
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.