[英]ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'
I know there are a lot of same questions already posted in stack-overflow and tried different solutions to avoid the run-time error but None of them are working for me.我知道堆栈溢出中已经发布了很多相同的问题,并尝试了不同的解决方案来避免运行时错误,但没有一个对我有用。
Component and Html Code组件和 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请注意:此组件是使用 DynamicComponentLoader 动态加载的
After My trouble shooting I have identified couple of issues在解决问题后,我发现了几个问题
First of all this child component is loaded dynamically by using DynamicComponentResolver and passing the input values like below首先,这个子组件是通过使用 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;
}
Even If I changed my child component html like below I am getting this same error.Just add a angular ngclass attribute即使我像下面这样更改了我的子组件 html,我也会收到同样的错误。只需添加一个 angular ngclass 属性
<div class="container-fluid ds-iconwidget-wrapper text-center" [ngClass]="Sample">
</div>
My databinding and everything are working fine.Do I need to do anything on parent component?我的数据绑定和一切工作正常。我需要对父组件做任何事情吗? I already tried all the lifecyle events in child component
我已经尝试了子组件中的所有生命周期事件
you have to tell angular
that you updated the content after ngAfterContentChecked
you can import ChangeDetectorRef
from @angular/core
and call detectChanges
你要告诉
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();
}
The ngAfterContentChecked
lifecycle hook is triggered when bindings updates for the child components/directives have been already been finished.当子组件/指令的绑定更新已经完成时,将触发
ngAfterContentChecked
生命周期钩子。 But you're updating the property that is used as a binding input for the ngClass
directive.但是您正在更新用作
ngClass
指令的绑定输入的ngClass
。 That is the problem.这就是问题所在。 When Angular runs validation stage it detects that there's a pending update to the properties and throws the error.
当 Angular 运行验证阶段时,它检测到属性有待处理的更新并抛出错误。
To understand the error better, read these two articles:要更好地理解错误,请阅读这两篇文章:
ExpressionChangedAfterItHasBeenCheckedError
error ExpressionChangedAfterItHasBeenCheckedError
错误的所有信息Think about why you need to change the property in the ngAfterViewInit
lifecycle hook.想想为什么需要更改
ngAfterViewInit
生命周期钩子中的属性。 Any other lifecycle that is triggered before ngAfterViewInit/Checked
will work, for example ngOnInit
or ngDoCheck
or ngAfterContentChecked
.在
ngAfterViewInit/Checked
之前触发的任何其他生命周期都可以工作,例如ngOnInit
或ngDoCheck
或ngAfterContentChecked
。
So to fix it move renderWidgetInsideWidgetContainer
to the ngOnInit()
lifecycle hook.所以为了修复它,将
renderWidgetInsideWidgetContainer
移动到ngOnInit()
生命周期钩子。
ngAfterViewInit() {
setTimeout(() => {
this.renderWidgetInsideWidgetContainer();
}, 0);
}
That is a good solution to resolve this problem.这是解决此问题的好方法。
If you are using <ng-content>
with *ngIf
you are bound to fall into this loop.如果您将
<ng-content>
与*ngIf
您一定会陷入这个循环。
Only way out I found was to change *ngIf
to display:none
functionality我发现的唯一出路是将
*ngIf
更改为display:none
功能
I was having trouble with .我遇到了麻烦。
ERROR: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
错误:ExpressionChangedAfterItHasBeenCheckedError:检查后表达式已更改。 Previous value for 'mat-checkbox-checked': 'true'.
'mat-checkbox-checked' 的先前值:'true'。 Current value: 'false'.
当前值:'假'。
The Problem here is that the updated value is not detected until the next change Detection Cycle runs.这里的问题是在下一个更改检测周期运行之前不会检测到更新的值。
The easiest solution is to add a Change Detection Strategy.最简单的解决方案是添加变更检测策略。 Add these lines to your code:
将这些行添加到您的代码中:
import { ChangeDetectionStrategy } from "@angular/core"; // import
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "abc",
templateUrl: "./abc.html",
styleUrls: ["./abc.css"],
})
This shall solve the problem这将解决问题
constructor(private changeDetector: ChangeDetectorRef) {}
ngAfterViewChecked() {
this.changeDetector.detectChanges();
}
Two Solutions:两种解决方案:
*NgIf
会在这里产生问题,所以要么使用 display none CSS 或更简单的方法是使用[hidden]="!condition"
setTimeout(() => { // your code here }, 0);
我用 setTimeout 包装了我的代码并且它起作用了
I had the same issue trying to do something the same as you and I fixed it with something similar to Richie Fredicson's answer.我在尝试做与您相同的事情时遇到了同样的问题,并且我用类似于 Richie Fredicson 的回答的方法修复了它。
When you run createComponent() it is created with undefined input variables.当您运行 createComponent() 时,它是使用未定义的输入变量创建的。 Then after that when you assign data to those input variables it changes things and causes that error in your child template (in my case it was because I was using the input in an ngIf, which changed once I assigned the input data).
然后,当您将数据分配给这些输入变量时,它会更改内容并导致您的子模板中出现该错误(在我的情况下,这是因为我在 ngIf 中使用了输入,一旦我分配了输入数据,就会发生变化)。
The only way I could find to avoid it in this specific case is to force change detection after you assign the data, however I didn't do it in ngAfterContentChecked().在这种特定情况下,我能找到避免它的唯一方法是在分配数据后强制更改检测,但是我没有在 ngAfterContentChecked() 中这样做。
Your example code is a bit hard to follow but if my solution works for you it would be something like this (in the parent component):您的示例代码有点难以理解,但如果我的解决方案适合您,它将是这样的(在父组件中):
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();
}
}
Mine is almost the same as that except I'm using @ViewChildren instead of @ViewChild as I have multiple host elements.我的几乎和那个一样,除了我使用 @ViewChildren 而不是 @ViewChild 因为我有多个宿主元素。
This code solved my Problem.这段代码解决了我的问题。
import {ChangeDetectorRef } from '@angular/core';
constructor( private cdref: ChangeDetectorRef ) {}
ngAfterContentChecked() {
this.cdref.detectChanges();
}
Like others suggested you can solve the issue by placing your code inside a setTimeout(..)
function.就像其他人建议的那样,您可以通过将代码放在
setTimeout(..)
函数中来解决问题。 It will essentially move the execution outside the event-loop stack, and hence into Angular's next change-detection cycle.它本质上会将执行移到事件循环堆栈之外,从而进入 Angular 的下一个更改检测周期。
RxJS has its own timeout implementation called timer()
- it accepts milliseconds as an argument and returns an observable. RxJS 有自己的超时实现,称为
timer()
- 它接受毫秒作为参数并返回一个可观察的。 Since we only need the execution to happen after the event loop stack is clean (and Angular has finished all its rendering calculations), we can use timer()
without any argument (it'll anyway default to 0):由于我们只需要在事件循环堆栈干净(并且 Angular 完成所有渲染计算)后执行,我们可以使用
timer()
不带任何参数(它无论如何都会默认为 0):
ngAfterViewInit() {
timer().subscribe(() => {
// your code here
})
}
JS event loop schema: JS 事件循环架构:
PS: My error details vary slightly, in the sense that mine detected a change in mat-focused
value from true
to false
. PS:我的错误细节略有不同,因为我检测到
mat-focused
值从true
到false
的变化。
During my research, I noticed that the error was thrown because I tried to bind the SelectedIndex
property of mat-tab-group
to the index of the selected item in a mat-select
field inside tab 1
of the tab group (code excerpt below).在我的研究过程中,我注意到错误被抛出,因为我试图将
mat-tab-group
的SelectedIndex
属性绑定到选项卡组的tab 1
内的mat-select
字段中所选项目的索引(下面的代码摘录) .
HTML extract: HTML摘录:
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<mat-tab-group animationDuration="500ms" [selectedIndex]="titleId.value">
<mat-tab label="Tab 1">
<mat-radio-group formControlName="titleId">
<mat-radio-button value="1">Mr</mat-radio-button>
<mat-radio-button value="2">Mrs</mat-radio-button>
</mat-radio-group>
</mat-tab>
<mat-tab label="tab 2">
...
</mat-tab>
<mat-tab label="tab 3">
...
</mat-tab>
</mat-tab-group>
</form>
Although my code worked as expected (ie switching tabs based on a selected item from the dropdown list), the error was pretty frustrating to be ignored.虽然我的代码按预期工作(即根据从下拉列表中选择的项目切换选项卡),但忽略该错误非常令人沮丧。 Unfortunately, no suggested solution here worked for me.
不幸的是,这里没有建议的解决方案对我有用。 However, I noticed that moving
mat-radio-group
outside the mat-tab-group
component, fixed the issue.但是,我注意到将
mat-radio-group
移到 mat mat-tab-group
组件之外,解决了这个问题。
New implementation:新实施:
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<mat-radio-group formControlName="titleId">
<mat-radio-button value="1">Mr</mat-radio-button>
<mat-radio-button value="2">Mrs</mat-radio-button>
</mat-radio-group>
<mat-tab-group animationDuration="500ms" [selectedIndex]="titleId.value">
<mat-tab label="Tab 1">
...
</mat-tab>
<mat-tab label="tab 2">
...
</mat-tab>
<mat-tab label="tab 3">
...
</mat-tab>
</mat-tab-group>
</form>
I expected the code below to fix the issue, but it didn't.我希望下面的代码能够解决问题,但事实并非如此。 Maybe I wasn't doing something right.
也许我做的事情不对。
abc.component.ts code extract abc.component.ts 代码摘录
ngAfterViewInit() {
setTimeout(() => {
this.cdRef.detectChanges(); /*cdRef injected in constructor*/
}, 0);
}
I still believe there is a better way to fix this.我仍然相信有更好的方法来解决这个问题。
Try this, to call your code in ngOnInit()试试这个,在ngOnInit() 中调用你的代码
someMethod() // emitted method call from output
{
// Your code
}
ngOnInit(){
someMethod(); // call here your error will be gone
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.