ExpressionChangedAfterItHasBeenCheckedError: 检查表达式后,表达式已更改

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

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>

Please Note : This Component is loaded dynamically using DynamicComponentLoader

After My trouble shooting I have identified couple of issues

首先,通过使用 DynamicComponent entResolver 并传递下面这些输入值,可以动态加载这个子组件

 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,我也会得到同样的错误。只需添加一个有角度的 ngclass 属性

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


</div>

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

230433 次浏览

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

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

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

因此,要修复它,请将 renderWidgetInsideWidgetContainer移动到 ngOnInit()生命周期挂钩。

你必须告诉 angular你在 ngAfterContentChecked之后更新了内容 您可以从 @angular/core导入 ChangeDetectorRef并调用 detectChanges

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


constructor( private cdref: ChangeDetectorRef ) {}




ngAfterContentChecked() {


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


}

我也有过和你一样的问题我用类似 Richie Fredicson 的答案来解决。

当您运行 createComponent ()时,它是用未定义的输入变量创建的。 然后,当您将数据分配给这些输入变量时,它会改变一些东西,并导致子模板中出现这个错误(在我的例子中,这是因为我在 ngIf 中使用了输入,在分配输入数据之后,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,因为我有多个主机元素。

If you are using <ng-content> with *ngIf you are bound to fall into this loop.

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

两种解决方案:

  1. 确保如果您有一些绑定变量,然后移动该代码到 暂停({} ,0) ;
  2. 将相关代码移动到 ngAfterViewInit方法
setTimeout(() => { // your code here }, 0);

我用 setTimeout 包装了我的代码,它工作了

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


这是解决这个问题的好办法。

*NgIf可能在这里创建一个问题,所以要么使用显示没有 CSS 或更简单的方法是使用 [hidden]="!condition"

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

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


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

我遇到了麻烦。

ERROR: ExpressionChangedAfterItHasBeenCheckedError: 表达式在选中后发生了更改。“ mat-checkbox-check”的上一个值为“ true”。当前值为“ false”。

这里的问题是直到下一个变化检测循环运行时才能检测到更新的值。

最简单的解决方案是增加一个变化检测策略。 将以下代码行添加到代码中:

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


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

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

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


constructor( private cdref: ChangeDetectorRef ) {}


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

RxJS timer ()

像其他人建议的那样,您可以通过将代码放在 setTimeout(..)函数中来解决这个问题。它实际上将执行移出事件循环堆栈,进而进入 Angular 的下一个变更检测周期。

RxJS 的 自己的超时实现称为 timer()——它接受毫秒作为参数,并返回一个可观察的值。因为我们只需要在事件循环堆栈被清除之后执行(而且 Angular 已经完成了所有的渲染计算) ,所以我们可以使用 timer()而不需要任何参数(它将默认为0) :

ngAfterViewInit() {
timer().subscribe(() => {
// your code here
})
}


JS 事件循环模式:

enter image description here

这将解决问题

  constructor(private changeDetector: ChangeDetectorRef) {}


ngAfterViewChecked() {
this.changeDetector.detectChanges();
}