224

My component has styles that depend on current datetime. In my component I've got the following function.

  private fontColor( dto : Dto ) : string {
    // date d'exécution du dto
    let dtoDate : Date = new Date( dto.LastExecution );

    (...)

    let color =  "hsl( " + hue + ", 80%, " + (maxLigness - lightnessAmp) + "%)";

    return color;
  }

lightnessAmp is calculated from the current datetime. The color changes if dtoDate is in the last 24 hours.

The exact error is the following:

Expression has changed after it was checked. Previous value: 'hsl( 123, 80%, 49%)'. Current value: 'hsl( 123, 80%, 48%)'

I know the exception appear in development mode only at the moment the value is checked. If the checked value is different of the updated value, the exception is thrown.

So I tried to update the current datetime at each lifecycle in the following hook method to prevent the exception:

  ngAfterViewChecked()
  {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
  }

...but without success.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Anthony Brenelière
  • 60,646
  • 14
  • 46
  • 58
  • This might be helpful to understand the problem: [Angular Debugging "Expression has changed after it was checked": Simple Explanation (and Fix)](https://blog.angular-university.io/angular-debugging/) – deamon Nov 08 '19 at 15:56

13 Answers13

473

Run change detection explicitly after the change:

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

constructor(private cdRef:ChangeDetectorRef) {}

ngAfterViewChecked()
{
  console.log( "! changement de la date du composant !" );
  this.dateNow = new Date();
  this.cdRef.detectChanges();
}
rmcsharry
  • 5,363
  • 6
  • 65
  • 108
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    Perfect solution, thank you. I noticed it works also with the following hook methods: ngOnChanges, ngDoCheck, ngAfterContentChecked. So is there a best option ? – Anthony Brenelière Sep 30 '16 at 09:33
  • 37
    That depends on your use case. If you want to do something when a component is initialized, `ngOnInit()` is usually the first place. If the code depends on the DOM being rendered `ngAfterViewInit()` or `ngAfterContentInit()` are the next options. `ngOnChanges()` is a good fit if the code should be executed every time an input was changed. `ngDoCheck()` is for custom change detection. Actually I don't know what `ngAfterViewChecked()` is best used for. I think it's called just before or after `ngAfterViewInit()`. – Günter Zöchbauer Sep 30 '16 at 09:37
  • I'm attempting to change this.value to prevent an invalid value from being set, however the technique described here doesn't work for me. Is there some other way I should do this? – Dave Nottage Jun 27 '17 at 05:37
  • @DaveNottage I think it should work. Perhaps it would be better to create a new question with your code that demonstrates what you try to accomlish. – Günter Zöchbauer Jun 27 '17 at 18:06
  • I'm struggling to implement this technique in my situation as well but I'm not having any luck, and it certainly seems as though I'm unsure where the problem is occurring. Would someone be able to look at mine? https://stackoverflow.com/questions/46982174/how-to-handle-my-particular-case-of-expression-has-changed-after-it-has-been-ch – David Oct 30 '17 at 15:26
  • Is there any way to run `ngAfterViewChecked()` in .ts file? – Kushal Jayswal Jun 13 '18 at 05:54
  • @KushalJayswal it's a component lifecycle event an called by Angulars change detection. It is not supposed to be called from your code. You can always move the code to another method can call it from an Angular event handler or an observable subscription callback. – Günter Zöchbauer Jun 13 '18 at 06:47
  • @GünterZöchbauer, I know and hence asked the question. I have a scenario where I want to open a modal if input is unchecked. And inside modal there are 2 buttons - Yes and No. While clicking on Yes that unchecked input should be checked and closed the modal.... So looking for a solution that can be used directly inside template. This checkbox can be repeated through `*ngFor`... – Kushal Jayswal Jun 13 '18 at 07:06
  • 2
    @KushalJayswal sorry, can't make sense from your description. I'd suggest to create a new question with the code that demonstrates what you try to accomplish. Ideally with a StackBlitz example. – Günter Zöchbauer Jun 13 '18 at 07:07
  • 4
    This is also an excellent solution if your component state is based on browser-calculated DOM properties, like `clientWidth`, etc. – Jonah Jun 15 '18 at 01:27
  • Causes a change detection loop if called in subscription inside any of hooks. – Ravinder Payal Nov 25 '18 at 12:32
  • it works also when you create a ControlValueAccessor component in the writeValue method – Diego Fernando Murillo Valenci Oct 02 '19 at 20:01
  • 1
    this answer was the best answer that i saw in my life. – itty Apr 20 '23 at 08:54
62

TL;DR

ngAfterViewInit() {
    setTimeout(() => {
        this.dateNow = new Date();
    });
}

Although this is a workaround, sometimes it's really hard to solve this issue in any nicer way, so don't blame yourself if you are using this approach. That's okay.

Examples: The initial issue [link], Solved with setTimeout() [link]


How to avoid

In general this error usually happens after you add somewhere (even in parent/child components) ngAfterViewInit. So first question is to ask yourself - can I live without ngAfterViewInit? Perhaps you move the code somewhere ( ngAfterViewChecked might be an alternative).

Example: [link]


Also

Also async stuff in ngAfterViewInit that affects DOM might cause this. Also can be solved via setTimeout or by adding the delay(0) operator in the pipe:

ngAfterViewInit() {
  this.foo$
    .pipe(delay(0)) //"delay" here is an alternative to setTimeout()
    .subscribe();
}

Example: [link]


Nice Reading

Good article about how to debug this and why it happens: link

S Panfilov
  • 16,641
  • 17
  • 74
  • 96
  • 4
    Seems to be slower than the selected solution – Pete B Jun 15 '18 at 15:54
  • 9
    This is not the best solution, but damn this works ALWAYS. The selected answer doesn't always work (one needs to understand the hook quite well to get it working) – Freddy Bonda Nov 16 '18 at 05:43
  • What is the correct explanation why this works, anyone knows? Is it because it then runs in a different Thread (async)? – knnhcn Nov 27 '18 at 08:53
  • 5
    @knnhcn There's no such thing as different thread in javascript. JS is single-threaded by nature. SetTimeout just tells the engine to execute the function *some time after the timer expires.* Here the timer is 0, which is actually treated as 4 in modern browsers, which is plenty of time for angular to do its magic thingies: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout – Kilves Dec 05 '18 at 10:43
  • @FreddyBonda i ended up with the same result. I usually subscribe Observable that needs to interact with the DOM in "ngAfterViewInit" cause other hooks doesn't work in some scenario. Every time i do i thing like this, i usually get the reported error and the only way to get rid of this is by adding delay(0) to the observable pipeline. Don't know if it's a good desing, but as long as it works.... – GiveEmTheBoot Nov 12 '20 at 22:29
37

Here you go two solutions!


1. Modify ChangeDetectionStrategy to OnPush

For this solution, you're basically telling angular:

Stop checking for changes; I'll do it only when I know is necessary

Modify your component so it'll use ChangeDetectionStrategy.OnPush like this:

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
    // ...
}

With this, things don’t seem to work anymore. That's because from now on you'll have to make Angular call the detectChanges() manually.

this.cdr.detectChanges();

If you're interested, please check this article. It helped me understand how ChangeDetectionStrategy works.


2. Understanding ExpressionChangedAfterItHasBeenCheckedError

Please check this video about the issue (is great!). Also, here is a small extract from this article about the causes for this error, I've tried to include only the parts that helped me to understand this.

The full article shows real code examples about every point shown here.

The root cause is angular lifecycle's:

After each operation Angular remembers what values it used to perform an operation. They are stored in the oldValues property of the component view.

After the checks have been done for all components Angular then starts the next digest cycle but instead of performing operations it compares the current values with the ones it remembers from the previous digest cycle.

The following operations are being checked at digest cycles:

check that values passed down to the child components are the same as the values that would be used to update properties of these components now.

check that values used to update the DOM elements are the same as the values that would be used to update these elements now perform the same.

checks for all child components

And so, the error is thrown when the compared values are different., blogger Max Koretskyi stated:

The culprit is always the child component or a directive.

And finally here are some real-world samples that usually cause this error:

  • Shared services (example)
  • Synchronous event broadcasting (example)
  • Dynamic component instantiation (example)

In my case, the problem was a dynamic component instantiation.

Also, from my own experience, I strongly recommend everyone to avoid the setTimeout solution, in my case caused an "almost" infinite loop (21 calls which I'm not willing to show you how to provoke them),

I would recommend always keeping in mind the Angular life cycle's so you can take into account how they would be affected every time you modify another component's value. With this error Angular is telling you:

You're maybe doing this the wrong way, are you sure you're right?

The same blog also says:

Often, the fix is to use the right change detection hook to create a dynamic component


A short guide for me is to consider at least the following things while coding:

(I'll try to complement it over time):

  1. Avoid modifying parent component values from its child's components, instead: modify them from their parent.
  2. When you use @Input and @Output directives try to avoid triggering lifecycle changes unless the component is completely initialized.
  3. Avoid unnecessary calls of this.cdr.detectChanges(); they can trigger more errors, especially when you're dealing with a lot of dynamic data
  4. When the use of this.cdr.detectChanges(); is mandatory make sure that the variables (@Input, @Output, etc) being used are filled/initialized at the right detection hook (OnInit, OnChanges, AfterView, etc)
  5. When possible, remove rather than hide, this is related to point 3 and 4. (same quote for angulardart)
  6. Avoid any kind of logic inside setters annotated with @Input, setters are executed previously to ngAfterViewInit so it'll easily trigger the issue. In case you need to, its better of to put that logic inside the ngOnChanges method.

Also

If you want to fully understand Angular Life Hook I recommend you to read the official documentation here: https://angular.io/guide/lifecycle-hooks

rofrol
  • 14,438
  • 7
  • 79
  • 77
luiscla27
  • 4,956
  • 37
  • 49
  • 1
    I still cannot understand why changing the `ChangeDetectionStrategy` to `OnPush` fixed it for me. I have a simple component, which has `[disabled]="isLastPage()"`. That method is reading the `MatPaginator`-ViewChild and returning `this.paginator !== undefined ? this.paginator.pageIndex === this.paginator.getNumberOfPages() - 1 : true;`. The paginator is not available right away, but after it has been bound using `@ViewChild`. Changing the `ChangeDetectionStrategy` removed the error - and the functionality still exists just like before. Not sure which drawbacks I now have, but thank you! – Igor Apr 12 '20 at 10:46
  • That's great @Igor! the only withdraw of using `OnPush` is that you'll have to use `this.cdr.detectChanges()` everytime you want you'r component to refresh. I guess you were already using it – luiscla27 Apr 13 '20 at 14:31
33

As mentioned by @leocaseiro on github issue.

I found 3 solutions for those who are looking for easy fixes.

1) Moving from ngAfterViewInit to ngAfterContentInit

2) Moving to ngAfterViewChecked combined with ChangeDetectorRef as suggested on #14748 (comment)

3) Keep with ngOnInit() but call ChangeDetectorRef.detectChanges() after your changes.

Pipo
  • 4,653
  • 38
  • 47
candidJ
  • 4,289
  • 1
  • 22
  • 32
  • 1
    Can anyone document what is the most recommended solution? – Pipo Feb 10 '19 at 14:41
  • @Pipo, it depends on your use case. The order shown in this answer is the recommended order of solution. However, if you include other answers shown in the question, the 3rd recommended solution would be to switch to `ChangeDetectionStrategy.OnPush`, 4th is the `detectChanges` and the 5th would be the `setTimeout` solution. The recommended order I just wrote is based on my mere experience from the ones which in the long run have less maintenance cost. – luiscla27 Jul 08 '22 at 23:12
13

In our case we FIXED by adding changeDetection into the component and call detectChanges() in ngAfterContentChecked, code as follows

@Component({
  selector: 'app-spinner',
  templateUrl: './spinner.component.html',
  styleUrls: ['./spinner.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SpinnerComponent implements OnInit, OnDestroy, AfterContentChecked {

  show = false;

  private subscription: Subscription;

  constructor(private spinnerService: SpinnerService, private changeDedectionRef: ChangeDetectorRef) { }

  ngOnInit() {
    this.subscription = this.spinnerService.spinnerState
      .subscribe((state: SpinnerState) => {
        this.show = state.show;
      });
  }

  ngAfterContentChecked(): void {
      this.changeDedectionRef.detectChanges();
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

}
Charitha Goonewardena
  • 4,418
  • 2
  • 36
  • 38
5

A small work around I used many times

Promise.resolve(data).then(() => {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
    this.cdRef.detectChanges();
});
Khateeb321
  • 1,861
  • 23
  • 25
4

Move your code from ngAfterViewInit to ngAfterContentInit.

The view is initialized after the content and ngAfterViewInit() is therefore called after ngAfterContentInit()

luiscla27
  • 4,956
  • 37
  • 49
Achraf Farouky
  • 813
  • 10
  • 11
2

Use a default form value to avoid the error.

Instead of using the accepted answer of applying detectChanges() in ngAfterViewInit() (which also solved the error in my case), I decided instead to save a default value for a dynamically required form field, so that when the form is later updated, it's validity is not changed if the user decides to change an option on the form that would trigger the new required fields (and cause the submit button to be disabled).

This saved a tiny bit of code in my component, and in my case the error was avoided altogether.

rofrol
  • 14,438
  • 7
  • 79
  • 77
T. Bulford
  • 387
  • 5
  • 8
2

I got that error because I declared a variable and later wanted to
changed it's value using ngAfterViewInit

export class SomeComponent {

    header: string;

}

to fix that I switched from

ngAfterViewInit() { 

    // change variable value here...
}

to

ngAfterContentInit() {

    // change variable value here...
}
user12163165
  • 555
  • 8
  • 12
1

I think the best and cleanest solution you can imagine is this:

@Component( {
  selector: 'app-my-component',
  template: `<p>{{ myData?.anyfield }}</p>`,
  styles: [ '' ]
} )
export class MyComponent implements OnInit {
  private myData;

  constructor( private myService: MyService ) { }

  ngOnInit( ) {
    /* 
      async .. await 
      clears the ExpressionChangedAfterItHasBeenCheckedError exception.
    */
    this.myService.myObservable.subscribe(
      async (data) => { this.myData = await data }
    );
  }
}

Tested with Angular 5.2.9

JavierFuentes
  • 1,840
  • 18
  • 13
  • 5
    This is hacky .. and so unneccesairy. – JoeriShoeby May 01 '18 at 11:47
  • 1
    @JoeriShoeby all other solutions above are workarounds based on setTimeouts or advanced Angular events... this solution is pure ES2017 supported by all majors browsers https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await https://caniuse.com/#search=await – JavierFuentes May 01 '18 at 12:23
  • 2
    What if you're building a mobile application (targeting Android API 17) using Angular + Cordova for example, where you can't rely of ES2017 features? Note the accepted answer is a solution, not a work-around.. – JoeriShoeby May 01 '18 at 12:26
  • @JoeriShoeby Using typescript, it's compiled to JS, so no feature support issues. – biggbest Sep 27 '18 at 15:03
  • @biggbest What do you mean with that? I know Typescript will be compiled to JS, but that does not makes you able to assume all JS will work. Note that Javascript is runned in a webbrowser, and every browser behaves different on it. – JoeriShoeby Nov 14 '18 at 15:25
1

Although there are many answers already and a link to a very good article on change detection, I wanted to give my two cents here. I think the check is there for a reason so I thought about the architecture of my app and realized that the changes in the view can be dealt with by using BehaviourSubject and the correct lifecycle hook. So here's what I did for a solution.

  • I use a third-party component (fullcalendar), but I also use Angular Material, so although I made a new plugin for styling, getting the look and feel was a bit awkward because customization of the calendar header is not possible without forking the repo and rolling your own.
  • So I ended up getting the underlying JavaScript class, and need to initialize my own calendar header for the component. That requires the ViewChild to be rendered befor my parent is rendered, which is not the way Angular works. This is why I wrapped the value I need for my template in a BehaviourSubject<View>(null):

    calendarView$ = new BehaviorSubject<View>(null);
    

Next, when I can be sure the view is checked, I update that subject with the value from the @ViewChild:

  ngAfterViewInit(): void {
    // ViewChild is available here, so get the JS API
    this.calendarApi = this.calendar.getApi();
  }

  ngAfterViewChecked(): void {
    // The view has been checked and I know that the View object from
    // fullcalendar is available, so emit it.
    this.calendarView$.next(this.calendarApi.view);
  }

Then, in my template, I just use the async pipe. No hacking with change detection, no errors, works smoothly.

Please don't hesitate to ask if you need more details.

thomi
  • 1,603
  • 1
  • 24
  • 31
0

All previous solutions never worked for me, this is the only one that actually did.

Error message example :

AppComponent.html:1 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: true'. Current value: 'ngIf: false'. at viewDebugError (core.js:20440) at expressionChangedAfterItHasBeenCheckedError (core.js:20428) ...

Solution In the component that triggers the problem import ChangeDetectorRef, AfterContentChecked Then add :

 ngAfterContentChecked(): void {
    this.changeDetector.detectChanges();
  }

Example

import { Component, OnInit, ChangeDetectorRef, AfterContentChecked } from '@angular/core';

import { Title } from '@angular/platform-browser';

import { environment } from '@env/environment';
import { LayoutService } from '@app/core/layout/layout.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: []
})
export class AppComponent implements OnInit, AfterContentChecked {
  env = environment;
  title = this.env.appName;
  partials: any = {};

  public constructor(
    private titleService: Title,
    public layout: LayoutService,
    private changeDetector: ChangeDetectorRef,
  ) {}

   ngOnInit() {
    this.titleService.setTitle(this.env.appName);
    
    this.layout.getLayout().subscribe(partials => {
      this.partials = partials;
    });
  }

  ngAfterContentChecked(): void {
    this.changeDetector.detectChanges();
  }
}

Not mine : here is the source https://gist.github.com/vades/a225170304f34f88a7a5d48bf4b1f58c

Igor Beaufils
  • 848
  • 2
  • 12
  • 29
0
  • You could run the setting of the property in the next macro task queue of the javascript event loop with µ setTimeout().
  • However in this case using detectChanges() would be enough. There are bunch of reasons when this error occurs:

All the reasons how to get this error and all the solutions to this error are explained in this article: https://blog.simplified.courses/angular-expression-changed-after-it-has-been-checked-error/

Brecht Billiet
  • 235
  • 1
  • 6