Dealing with the "expression has changed after it was checked" error in Angular 2, specifically when a component property relies on the current datetime

My component's styling is dependent on the current datetime. I have a function within my component that looks like this:

  private fontColor( dto : Dto ) : string {
    // date of execution for the dto
    let dtoDate : Date = new Date( dto.LastExecution );

    (...)

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

    return color;
  }

The value of lightnessAmp changes based on the current datetime and if dtoDate falls within the last 24 hours, the color changes.

An error message is triggered as follows:

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

I understand that this exception occurs in development mode when the checked value does not match the updated value. It happens at the moment of checking the value.

To try to prevent this exception, I attempted updating the current datetime at each lifecycle using the following hook method:

  ngAfterViewChecked()
  {
    console.log( "! Changing the component's date !" );
    this.dateNow = new Date();
  }

However, this approach did not resolve the issue.

Answer №1

Trigger change detection manually following the update:

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

constructor(private cdRef:ChangeDetectorRef) {}

ngAfterViewChecked()
{
  console.log( "! Component date has changed !" );
  this.dateNow = new Date();
  this.cdRef.detectChanges();
}

Answer №2

Summary

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

While considered a workaround, sometimes resolving this issue can be challenging, and using this method is acceptable.

Examples: The initial problem [link], Resolved with setTimeout() [link]


Prevention

This error commonly occurs after adding ngAfterViewInit somewhere in the code (even in parent/child components). Consider if it's possible to avoid using ngAfterViewInit or moving the code to another lifecycle hook like ngAfterViewChecked.

Example: [link]


Additional Insights

Asynchronous actions in ngAfterViewInit impacting the DOM can trigger this issue. It can also be resolved by using setTimeout or the delay(0) operator in the pipe:

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

Example: [link]


Further Reading

Read an informative article on debugging this issue and understanding its causes: link

Answer №3

Here are two solutions for you!


1. Adjust ChangeDetectionStrategy to OnPush

In this solution, you're essentially informing Angular:

Stop checking for changes continuously; I will trigger it only when necessary

Update your component to utilize ChangeDetectionStrategy.OnPush like so:

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

After implementing this, the behavior may seem different. This is because you now need to manually call detectChanges() in Angular.

this.cdr.detectChanges();

If you want more insight, feel free to explore this article. It provided me with a better understanding of how ChangeDetectionStrategy operates.


2. Understanding ExpressionChangedAfterItHasBeenCheckedError

Please refer to this informative video about the error (very helpful!). Additionally, here is an extract from this article, focusing on the causes of this error, highlighting segments that aided my comprehension.

The complete article provides concrete code examples corresponding to each point discussed.

The primary issue lies within the Angular lifecycle:

Following every operation, Angular remembers the values utilized during the operation. These values are stored in the oldValues property of the component view.

Upon completing all component checks, Angular proceeds to the next digest cycle. Instead of executing operations, it compares current values to those remembered from the previous digest cycle.

During digest cycles, the following validations are performed:

Verifying that values passed down to child components match the ones used for updating their properties at present time.

Ensuring that values employed for updating DOM elements coincide with what would update these elements accurately and then perform the same action.

Checking all child components.

Consequently, the error arises when compared values differ. According to blogger Max Koretskyi:

The source of concern typically traces back to the child component or a directive.

Moreover, below are real-world scenarios commonly leading to this error:

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

In my scenario, the issue stemmed from dynamic component instantiation.

Furthermore, based on personal experience, I strongly advise against utilizing the setTimeout approach which, in my case, led to an "almost" infinite loop (21 calls that I prefer not elaborating on).

I recommend staying mindful of Angular's life cycles during development, considering how alterations in one component may impact others. With this error, Angular is signaling:

You might be approaching this incorrectly; are you certain about your methodology?

The aforementioned blog also states:

Typically, rectifying the situation involves utilizing the appropriate change detection hook for creating a dynamic component.


A quick guide emphasizing several coding considerations:

(Additional insights will likely be included gradually):

  1. Avoid modifying parent component values directly from its child components; instead, alter them from their parent counterpart.
  2. When utilizing @Input and @Output directives, strive to minimize triggering lifecycle adjustments until the component is fully initialized.
  3. Avert unnecessary invocations of this.cdr.detectChanges(); as they can potentially lead to additional errors, particularly when dealing with substantial dynamic data.
  4. When employing this.cdr.detectChanges(); becomes imperative, confirm that variables (@Input, @Output, etc.) being leveraged are populated/initiated within the appropriate detection hook (
    OnInit, OnChanges, AfterView, etc.
    ).
  5. Whenever feasible, opt to remove rather than conceal; this aligns with points 3 and 4 (same viewpoint for angulardart).
  6. Lay off incorporating intricate logic within setters annotated with @Input; such logic executes prior to
    ngAfterViewInit</code and can readily engender the issue. In instances where it's essential, consider relocating said logic into the <code>ngOnChanges
    method.

Additionally

To attain a comprehensive understanding of Angular Lifecycle Hooks, I encourage delving into the official documentation accessible here: https://angular.io/guide/lifecycle-hooks

Answer №4

As highlighted by @leocaseiro in a discussion on Github.

For those seeking simple solutions, here are 3 options:

1) Switching from ngAfterViewInit to ngAfterContentInit

2) Transitioning to ngAfterViewChecked along with ChangeDetectorRef as mentioned in issue #14748 (comment)

3) Stick with ngOnInit() but remember to call

ChangeDetectorRef.detectChanges()
after making changes.

Answer №5

We were able to resolve our issue by incorporating change detection in the component and invoking detectChanges() within ngAfterContentChecked, as shown in the code snippet below.

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

}

Answer №6

An effective workaround that has proven useful on numerous occasions

Promise.resolve(data).then(() => {
    console.log( "! changing the component date !" );
    this.dateNow = new Date();
    this.cdRef.detectChanges();
});

Answer №7

Transfer your code from ngAfterViewInit to ngAfterContentInit.

The content is initialized before the view, so it's best to use ngAfterContentInit() instead of ngAfterViewInit()

Answer №8

Encountered an error when trying to update the value of a variable after declaring it in ngAfterViewInit:

export class SomeComponent {

    header: string;

}

To resolve this issue, I made the switch from

ngAfterViewInit() { 

    // change variable value here...
}

to

ngAfterContentInit() {

    // change variable value here...
}

Answer №9

Prevent errors by setting a default form value.

Instead of following the advice to use detectChanges() in ngAfterViewInit(), I opted to assign a default value to a dynamically required form field. This way, the form's validity remains unchanged when users update the form and trigger new required fields, preventing the submit button from being disabled unintentionally.

This simple adjustment saved me some code in my component, and successfully prevented the error from occurring.

Answer №10

My suggestion for the most effective and efficient solution would be as follows:

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

  constructor( private myService: MyService ) { }

  ngOnInit( ) {
    /* 
      Using async..await within a subscription prevents ExpressionChangedAfterItHasBeenCheckedError.
    */
    this.myService.myObservable.subscribe(
      async (data) => { this.myData = await data }
    );
  }
}

This code has been tested with Angular version 5.2.9.

Answer №11

While there are already multiple responses and a reference to an informative article on change detection, I wanted to share my own insights. Upon careful consideration of the architecture of my application, it became apparent that managing view changes could be effectively handled by utilizing a BehaviourSubject in conjunction with the appropriate lifecycle hook. Here is how I resolved the issue:

  • Although I utilize a third-party component (fullcalendar), my application also makes use of Angular Material. While creating a new plugin for styling, I encountered challenges in achieving the desired appearance due to limitations in customizing the calendar header without modifying the repository extensively.
  • To address this, I accessed the underlying JavaScript class and needed to initialize a custom calendar header for the component. This necessitated ensuring that the ViewChild was rendered before its parent, contrary to Angular's default behavior. To work around this, I encapsulated the required value for my template within a

    BehaviourSubject<View>(null)
    :

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

Subsequently, once I could confirm that the view had been checked, I updated the subject with the value obtained from the @ViewChild:

ngAfterViewInit(): void {
    // Accessing the JS API as ViewChild is now available
    this.calendarApi = this.calendar.getApi();
  }

  ngAfterViewChecked(): void {
    // The View object from fullcalendar is accessible after the view check, so emit it
    this.calendarView$.next(this.calendarApi.view);
  }

Finally, in my template, I simply leveraged the async pipe. This approach eliminated the need for manual change detection manipulation, minimized errors, and ensured seamless functionality.

If you require further clarification or details, please feel free to reach out.

Answer №12

I struggled with finding a solution to my issue, but finally found one that actually worked.

Encountered Error:

When checking the value in AppComponent.html:1, I received this error message: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: true'. Current value: 'ngIf: false'.

Effective Solution: To address the issue, import ChangeDetectorRef and AfterContentChecked in the component causing the problem. Then, include the following code snippet:

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

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

If you need more information, check out the original source here.

Answer №13

  • If you want to execute the property setting in the following macro task queue of the JavaScript event loop, you can do so with the help of setTimeout().
  • However, using detectChanges() should suffice in this scenario. There are numerous reasons why this error might occur:

This article thoroughly explains the various causes of this error and provides solutions:

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

"Successful deletion with Express, yet error message of 'Not Found' displayed

I've implemented this boilerplate to build my API, utilizing express and typeorm with typescript. When attempting to delete a question, the deletion process works smoothly but I receive a 404 not found response. Below is my Question.ts class: @Entit ...

Tips on Identifying the Category

I am currently studying TypeScript. Recently, I have been using Axios to fetch API data, and then I stored the returned value in a useEffect hook. However, when trying to display it on the screen, I encountered an error stating that there is no 'name ...

Convert a TypeScript array of strings to a boolean array: a step-by-step guide

Upon receiving a list of objects from the front end as: item=["false","true"] I proceed to check a column within my records to identify values containing "true" or "false" using the following code: this.records.filter(x=> items.includes(x.column)) Unf ...

What is the reasoning behind defaultValue possessing the type of any in TextField Material UI?

According to the Material UI guidelines, the component TextField specifies that its defaultValue property accepts the type any. I decided to experiment with this a bit and found that in practice, defaultValue actually supports multiple types. You can see ...

Retrieving Child Route Parameters in Angular 7

Fetching the parameter 'id' only from the corresponding page component seems to be a challenge. The params id cannot be accessed from other individual components such as the header component. //The code snippet below works only in the correspond ...

Storing information using the DateRangePicker feature from rsuite - a step-by-step guide

Currently, I am working on storing a date range into an array using DateRangePicker from rsuite. Although my application is functioning correctly, I am encountering some TypeScript errors. How can I resolve this issue? import { DateRangePicker } from " ...

The SDK directory for TypeScript 1.3 in Visual Studio 2013 does not include the necessary tsc.exe file

Exciting news! Typescript v1.3 has been officially announced today. To fully utilize this update, I quickly installed the power tools update for VS2013. Upon completion of the installation, my Visual Studio environment now recognizes the "protected" keywo ...

The issue of updating a GLSL uniform variable during an animation in three.js using TypeScript remains unresolved

Running a three.js TypeScript application, I developed custom GLSL shaders using the THREE.ShaderMaterial() class. Now, I aim to enhance my code by switching to the THREE.MeshStandardMaterial class. This is an entirely new experience for me, and despite e ...

Attempting to incorporate alert feedback into an Angular application

I am having trouble referencing the value entered in a popup input field for quantity. I have searched through the documentation but haven't found a solution yet. Below is the code snippet from my .ts file: async presentAlert() { const alert = awa ...

Exploring the Power of Observables in Angular 2: Chaining and

Hi there! I'm relatively new to Angular and still getting the hang of observables. While I'm pretty comfortable with promises, I'd like to dive deeper into using observables. Let me give you a quick rundown of what I've been working on ...

Upon successfully logging into the app, Azure AD B2C integrated with MSAL and Angular will automatically redirect users to the login page

I recently updated my Angular app to make it accessible to customers by switching from ADAL to MSAL for authentication. I configured the app with Azure AD B2C credentials and everything seems to be working smoothly, except for one issue. When I try to logi ...

Angular Material's <mat-select> tag allows for the inclusion of an <input> element within it

I am attempting to place an input element inside the mat-select tag of the Angular Material element. However, I am facing an issue where I cannot input any text into the input field inside the select element. Below is the code I am using to search for elem ...

How to implement tree selection feature in Angular PrimeNG version 11.x on a tree component

I am currently working with primeng v11.x and utilizing p-tree to showcase hierarchy view with checkboxes. However, I would like to have this displayed within a dropdown similar to p-treeselect. Due to constraints, I am unable to upgrade primeng at the mom ...

Constructing an Angular 2 application using a solo TypeScript file that is compiled individually

At the moment, I am in the process of developing a Chrome Extension using Angular 2. The application includes a background.js file which handles the functionality for a long-running task that operates while the extension is active. Currently, this backgrou ...

A step-by-step guide on setting up a database connection with .env in typeorm

I encountered an issue while attempting to establish a connection with the database using ormconfig.js and configuring .env files. The error message I received was: Error: connect ECONNREFUSED 127.0.0.1:3000 at TCPConnectWrap.afterConnect [as oncomplete] ( ...

Sharing information between components in Angular 4 and .NET Core applications

I am new to Angular and .NET Core. I have successfully created a web api using .NET Core, which is called from an Angular 4 application. Currently, everything is working smoothly. However, after submitting a form that inserts records into the database, I w ...

Tips for executing a type-secure object mapping in Typescript

I am currently working on creating a function in Typescript that will map an object while ensuring that it retains the same keys. I have attempted different methods, but none seem to work as intended: function mapObject1<K extends PropertyKey, A, B>( ...

Apologies, but it seems there was an issue with the installation of the "@angular/compiler-cli" package

Despite thoroughly searching through various threads, I am still unable to find a solution to my problem. I have cloned the angular2 quickstart project and ensured that all module versions are up to date. For reference, here is the link to the repository ...

Flex: 1 does not completely fill the Angular layout div in vertical dimensions

I am currently developing a sidebar using Angular and Flex. The app-sidebar is nested within the Angular component layout shown below: <div fxlayout="row" fxFill> <app-sidebar fxLayout="column" fxFlex="10%"></app-sidebar> <div ...

"Unexpected Alignment Issue with NZ Zorro's Dynamic Columns Feature in the Ant Design NZ-Table Component

I am facing an issue with a table that receives columns dynamically from the server. The headers and data columns are not aligned properly. How can I ensure that they align correctly? <nz-table *ngIf="queryResults" #headerTable [nzData]="queryResults" ...