Reputation: 7931
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
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
First of all this child component is loaded dynamically by using DynamicComponentResolver and passing the input values like below
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
<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
Upvotes: 161
Views: 351633
Reputation: 1
After checking out the Angular doc on this, I solved it by injecting the ChangeDetectorRef (unlike another solution I found, NOT with the .OnPush method, which apparently overrides things):
constructor(private changeDetector: ChangeDetectorRef) {
ngAfterViewInit(){
this.changeDetector.detectChanges();
}
The overall problem is that the check comes BEFORE the value changes, thus the change detection has to be implemented additionally.
Upvotes: 0
Reputation: 124
This error occurs when Angular detects that a value has changed after it has been checked. To resolve this, you can try wrapping the code that updates the value in a setTimeout function. This will defer the update to the next tick of the event loop, preventing the error.
Upvotes: 0
Reputation: 10374
My best advise for debugging the error is putting a breakpoint in the throwErrorIfNoChangesMode
within the core.mjs
file. In this way, by analysing the parameters, you'll have all the information to find and address what's happening. Unfortunately, the Angular error message is not informative enough about what caused the error and where.
Upvotes: 0
Reputation: 105439
The ngAfterContentChecked
lifecycle hook is triggered when bindings updates for the child components/directives have already been finished. But you're updating the property that is used as a binding input for the ngClass
directive. That is the problem. When Angular runs validation stage it detects that there's a pending update to the properties and throws the error.
To understand the error better, read these two articles:
ExpressionChangedAfterItHasBeenCheckedError
errorThink about why you need to change the property in the ngAfterViewInit
lifecycle hook. Any other lifecycle that is triggered before ngAfterViewInit/Checked
will work, for example ngOnInit
, ngDoCheck
or ngAfterContentChecked
.
So in order to fix it, move renderWidgetInsideWidgetContainer
to the ngOnInit()
lifecycle hook.
Upvotes: 86
Reputation: 2453
you have to tell angular
that you updated the content after ngAfterContentChecked
you can import ChangeDetectorRef
from @angular/core
and call detectChanges
import { ChangeDetectorRef } from '@angular/core';
constructor( private cdref: ChangeDetectorRef ) {}
ngAfterContentChecked() {
this.sampleViewModel.DataContext = this.DataContext;
this.sampleViewModel.Position = this.Position;
this.cdref.detectChanges();
}
Upvotes: 184
Reputation: 4193
setTimeout(() => { /* your code here */ }, 0);
I wrapped my code in setTimeout and it worked
Upvotes: 4
Reputation: 53
PS: My error details vary slightly, in the sense that mine detected a change in mat-focused
value from true
to 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).
HTML extract:
<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 (i.e. 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.
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
ngAfterViewInit() {
setTimeout(() => {
this.cdRef.detectChanges(); /*cdRef injected in constructor*/
}, 0);
}
I still believe there is a better way to fix this.
Upvotes: 0
Reputation: 2932
Like others suggested you can solve the issue by placing your code inside a setTimeout(..)
function. It will essentially move the execution outside the event-loop stack, and hence into Angular's next change-detection cycle.
RxJS has its own timeout implementation called timer()
- it accepts milliseconds as an argument and returns an observable. 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):
ngAfterViewInit() {
timer().subscribe(() => {
// your code here
})
}
JS event loop schema:
Upvotes: 1
Reputation: 272
*NgIf
can create a problem here, so either use display none CSS or easier way is to Use [hidden]="!condition"
Upvotes: 4
Reputation: 351
I was having trouble with .
ERROR: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value for '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"],
})
Upvotes: 23
Reputation: 1737
Try this, to call your code in ngOnInit()
someMethod() // emitted method call from output
{
// Your code
}
ngOnInit(){
someMethod(); // call here your error will be gone
}
Upvotes: -2
Reputation: 449
ngAfterViewInit() {
setTimeout(() => {
this.renderWidgetInsideWidgetContainer();
}, 0);
}
That is a good solution to resolve this problem.
Upvotes: 27
Reputation: 1007
Two Solutions:
Upvotes: 6
Reputation: 6070
If you are using <ng-content>
with *ngIf
you are bound to fall into this loop.
Only way out I found was to change *ngIf
to display:none
functionality
Upvotes: 14
Reputation: 811
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.
When you run createComponent() it is created with undefined input variables. 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).
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().
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.
Upvotes: 2