ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'
Asked Answered
C

13

151

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.

Error

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

Cowardly answered 2/8, 2017 at 17:56 Comment(13)
how do you add TestComponent?Artimas
@Maximus as entry componentCowardly
@Maximus I tried by calling this.cdRef.detectChanges(); explicitly but not workingCowardly
I've posted explanation why the error happens. Why do you need to update the sampleViewModel in the ngAfterViewInit lifecycle hook?Artimas
@Maximus Initially I am directly binded the datacontext property to the template and then I have tried this ngAfterContentChecked . In both cases I am getting the error.Cowardly
what is unclear about my answer?Artimas
First of all move this.renderWidgetInsideWidgetContainer(); from ngAfterViewInit to ngOnInitSusansusana
@Maximus Thank you Maximus. My issue got resolved after move the this.renderWidgetInsideWidgetContainer(); to ngOnInit. actually in angular official documentation angular.io/guide/dynamic-component-loader they are using ngAfterViewInitCowardly
@Maximus If you can add the above information in answer it will be helpful to others and I can mark it as answerCowardly
@JEMI, added that to the answer. You can now accept it. Thanks. To understand why they can use ngAfterViewInit and you can't understand my answer and all the details mentioned in the articles I referenced.Artimas
@JEMI, you're welcome) I've got many interesting articles, do checkArtimas
@Maximus Sure. I am already your follower now. Your every article are in very depth.Cowardly
Official guideline for the error from Angular - angular.io/errors/NG0100Hyphen
A
78

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:

Think 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.

Artimas answered 2/8, 2017 at 18:1 Comment(10)
why are you updating the property in the ngAfterViewInit/Checked?Artimas
Actully it's not updating I am setting my input data to a viewmodel and finally all the properties are binded through this model like wrapper class. When I directly bind the @Input datacontext I am getting this same error. is there is no workaround to get rid of this error through any of the life cycle events.Let me try ngAfterViewInit/Checked mentioned in your answerCowardly
try ngOnInit or ngDoCheck or ngAfterContentChecked. If it's still not working, create a plunkerArtimas
hmm..It's weird for me , as per my understanding it's pretty straight forward scenario. I am suspecting it's because of dynamic Component loader. Let me try to create a plunker.Cowardly
I have tried all the life cycle events but unfortunately i couldn't fix the issue. but my question is updated by adding more points after my trouble shooting. hope this will helps you to get some idea.Cowardly
i need a plunkerArtimas
@Code-EZ Why is this chosen as the selected answer if you didn't get it to work? Or did you eventually get it to work?Aylmar
@rooby, maybe because I explained the cause of the error and the exact solutions heavily depend on the application designArtimas
@Aylmar after moved to ngOnInit() ,it's worked. You can see my comments with Maxim.Cowardly
For me work in ngOnInit only after i used a reference variable instead of a directory, like explaned here #48331260Encapsulate
B
171

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();
 }
Bonucci answered 2/8, 2017 at 18:2 Comment(0)
A
78

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:

Think 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.

Artimas answered 2/8, 2017 at 18:1 Comment(10)
why are you updating the property in the ngAfterViewInit/Checked?Artimas
Actully it's not updating I am setting my input data to a viewmodel and finally all the properties are binded through this model like wrapper class. When I directly bind the @Input datacontext I am getting this same error. is there is no workaround to get rid of this error through any of the life cycle events.Let me try ngAfterViewInit/Checked mentioned in your answerCowardly
try ngOnInit or ngDoCheck or ngAfterContentChecked. If it's still not working, create a plunkerArtimas
hmm..It's weird for me , as per my understanding it's pretty straight forward scenario. I am suspecting it's because of dynamic Component loader. Let me try to create a plunker.Cowardly
I have tried all the life cycle events but unfortunately i couldn't fix the issue. but my question is updated by adding more points after my trouble shooting. hope this will helps you to get some idea.Cowardly
i need a plunkerArtimas
@Code-EZ Why is this chosen as the selected answer if you didn't get it to work? Or did you eventually get it to work?Aylmar
@rooby, maybe because I explained the cause of the error and the exact solutions heavily depend on the application designArtimas
@Aylmar after moved to ngOnInit() ,it's worked. You can see my comments with Maxim.Cowardly
For me work in ngOnInit only after i used a reference variable instead of a directory, like explaned here #48331260Encapsulate
P
27
 ngAfterViewInit() {
   setTimeout(() => {
     this.renderWidgetInsideWidgetContainer();
   }, 0);
  }

That is a good solution to resolve this problem.

Participate answered 25/8, 2019 at 13:47 Comment(5)
None of the other solutions worked for my specific esoteric case so after a couple of hours I gave up and had to resort to this and it worked.Sorehead
I didn't understand how but it worked for me too.Mirabella
@FatihErsoy you can think of setTimeout() as putting something at the end of the overall JS event queue. So in this case, even with a Timeout of "0," it puts the code to run at the end of the events to run which means the code in Timeout will likely run after the Angular render code which is further ahead in the queue. Ultimately, setTimeout() feels like a hack solution, but when the Angular lifecycles aren't behaving as you expect and you're desperate to get it working then it's a tool you can resort to using.Gullet
I had this issue while loading a dialog component, with a list of checkboxes. By wrapping my code into the setTimeout() function, the dialog display is delayed long enough to get rid of that error.Counterpoise
Very sad that I have to use this, but it's the only thing that works!Hypochlorite
O
22

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"],
})
Obtrusive answered 7/9, 2020 at 21:11 Comment(1)
Dangerous: github.com/angular/angular/issues/23657#issuecomment-653939615Merridie
G
13

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

Gulley answered 1/2, 2019 at 0:56 Comment(1)
or can use [hidden]="!condition"Mycology
C
6

Two Solutions:

  1. Make Sure if you have some binding variables then move that code to settimeout( { }, 0);
  2. Move your related code to ngAfterViewInit method
Commiserate answered 7/2, 2019 at 6:10 Comment(2)
It depends as it moves your code to the event queue and it will get executed when the current execution finishes. JavaScript is a single-threaded scripting language, so it can execute one piece of code at a time (due to its single-threaded nature) each of these blocks of code is “blocking” the progress of other asynchronous events. The settimeout code will be executed only when your current code finishes your execution.Commiserate
hmm.. this fixed mine. I went to component and put all changes to bindings into a timeout of 0 secs and it works. is this a good solution or a hack? I am calling the method that changes binding value probably 2 times during the life cycle.Happygolucky
T
4
setTimeout(() => { /* your code here */ }, 0);

I wrapped my code in setTimeout and it worked

Toile answered 26/7, 2019 at 13:11 Comment(0)
M
4

*NgIf can create a problem here, so either use display none CSS or easier way is to Use [hidden]="!condition"

Mycology answered 22/4, 2020 at 10:25 Comment(0)
A
2

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.

Aylmar answered 16/1, 2019 at 8:46 Comment(0)
S
0

RxJS timer()

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:

enter image description here

Stairway answered 7/12, 2021 at 12:54 Comment(0)
R
0

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.

Rosemare answered 30/1, 2023 at 0:44 Comment(0)
L
0

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.

dev tools screenshoot

Linetta answered 15/2 at 10:1 Comment(0)
F
-2

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
}
Faitour answered 8/7, 2020 at 13:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.