[angular] How to manage Angular2 "expression has changed after it was checked" exception when a component property depends on current datetime

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.

This question is related to angular typescript time styles components

The answer is


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


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.


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();
  }

}

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.


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

Her you go two solutions!

1. Modify ChangeDetectionStrategy to OnPush

For this solution, you'r basically telling angular:

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

The quick fix:

Modify your component so it'll use ChangeDetectionStrategy.OnPush

@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();

Here's a link that helped me understand ChangeDetectionStrategy right: https://alligator.io/angular/change-detection-strategy/

2. Understanding ExpressionChangedAfterItHasBeenCheckedError

Here is a small extract from tomonari_t answer 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 the being checked at digest cycle's:

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
  • Synchronous event broadcasting
  • Dynamic component instantiation

Every sample can be found here (plunkr), in my case the problem was a dynamic component instantiation.

Also, by 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 to always keep in mind Angular life-cycle's so you can take in 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 two things while coding (i'll try to complement it over time):

  1. Avoid modifying parent component values from it's child's components, instead: modify them from its parent.
  2. When you use @Input and @Output directives try to avoid triggering lyfecycle changes unless the component is completely initialized.
  3. Avoid unnecessary calls of this.cdr.detectChanges(); they can trigger more errors, specially when you'r 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 right detection hook (OnInit, OnChanges, AfterView, etc)
  5. When possible, remove rather than hide, this is related to point 3 and 4.

Also

If you want to fully understand Angular Life Hook i recommend you to read the official documentation here:


A small work around I used many times

_x000D_
_x000D_
Promise.resolve(data).then(() => {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
    this.cdRef.detectChanges();
});
_x000D_
_x000D_
_x000D_


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


Move your code from ngAfterViewInit to ngAfterContentInit


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.


Examples related to angular

error NG6002: Appears in the NgModule.imports of AppModule, but could not be resolved to an NgModule class error TS1086: An accessor cannot be declared in an ambient context in Angular 9 TS1086: An accessor cannot be declared in ambient context @angular/material/index.d.ts' is not a module Why powershell does not run Angular commands? error: This is probably not a problem with npm. There is likely additional logging output above Angular @ViewChild() error: Expected 2 arguments, but got 1 Schema validation failed with the following errors: Data path ".builders['app-shell']" should have required property 'class' Access blocked by CORS policy: Response to preflight request doesn't pass access control check origin 'http://localhost:4200' has been blocked by CORS policy in Angular7

Examples related to typescript

TS1086: An accessor cannot be declared in ambient context Element implicitly has an 'any' type because expression of type 'string' can't be used to index Angular @ViewChild() error: Expected 2 arguments, but got 1 Typescript: No index signature with a parameter of type 'string' was found on type '{ "A": string; } Understanding esModuleInterop in tsconfig file How can I solve the error 'TS2532: Object is possibly 'undefined'? Typescript: Type 'string | undefined' is not assignable to type 'string' Typescript: Type X is missing the following properties from type Y length, pop, push, concat, and 26 more. [2740] Can't perform a React state update on an unmounted component TypeScript and React - children type?

Examples related to time

Date to milliseconds and back to date in Swift How to manage Angular2 "expression has changed after it was checked" exception when a component property depends on current datetime how to sort pandas dataframe from one column Convert time.Time to string How to get current time in python and break up into year, month, day, hour, minute? Xcode swift am/pm time to 24 hour format How to add/subtract time (hours, minutes, etc.) from a Pandas DataFrame.Index whos objects are of type datetime.time? What does this format means T00:00:00.000Z? How can I parse / create a date time stamp formatted with fractional seconds UTC timezone (ISO 8601, RFC 3339) in Swift? Extract time from moment js object

Examples related to styles

How to manage Angular2 "expression has changed after it was checked" exception when a component property depends on current datetime React Native Border Radius with background color How to change DatePicker dialog color for Android 5.0 How to change line color in EditText Valid values for android:fontFamily and what they map to? Overriding css style? WPF C# button style How to stick <footer> element at the bottom of the page (HTML5 and CSS3)? Binding ConverterParameter CSS align images and text on same line

Examples related to components

How to Refresh a Component in Angular Angular 4 - get input value Disable Button in Angular 2 How to Pass data from child to parent component Angular How to map an array of objects in React Checking for Undefined In React What's the difference between an Angular component and module React component initialize state from props How to manage Angular2 "expression has changed after it was checked" exception when a component property depends on current datetime React - how to pass state to another component