Angular: Expression Changed After It Has Been Checked Error

Loading

Angular: ExpressionChangedAfterItHasBeenCheckedError – Detailed Explanation & Solutions

Introduction

The ExpressionChangedAfterItHasBeenCheckedError is one of the most common errors encountered in Angular applications. This error occurs when Angular detects that a property has changed after the change detection cycle has already run. It is a safeguard built into Angular to prevent unintended side effects and ensure predictable application behavior.


Understanding the Error

When Does This Error Occur?

This error typically arises when:

  1. A component modifies a bound property inside a lifecycle hook (such as ngOnInit or ngAfterViewInit), causing a change after Angular has already checked the component.
  2. A value changes asynchronously (e.g., using setTimeout, Promises, Observables) but is still within the same change detection cycle.
  3. Parent and child components are out of sync because of timing issues with data binding.

Example Scenario

Consider a simple example where a component updates a property in ngOnInit:

@Component({
  selector: 'app-root',
  template: `<h1>{{ title }}</h1>`
})
export class AppComponent {
  title: string = 'Angular App';

  ngOnInit() {
    this.title = 'Updated Title';
  }
}

βœ… This works fine because title is updated before Angular finishes the change detection cycle.

However, if we introduce a setTimeout() inside ngOnInit:

ngOnInit() {
  setTimeout(() => {
    this.title = 'Updated Title';
  }, 0);
}

🚨 This will throw ExpressionChangedAfterItHasBeenCheckedError because the update happens after the initial change detection.


Why Does This Happen?

Angular runs a change detection cycle to update the DOM based on component state. The steps are as follows:

  1. Component Initialization
    • The component is created, and the constructor is called.
  2. Lifecycle Hooks Execution
    • ngOnInit() runs, allowing initial data modifications.
  3. Change Detection Runs
    • Angular checks for changes and updates the DOM.
  4. View Rendering
    • The updated values are reflected in the UI.
  5. Change Detection Runs Again (For Debug Mode)
    • In development mode, Angular runs a second check to catch unintended modifications.
  6. Error Thrown If Values Have Changed
    • If Angular detects a change between the first and second checks, it throws ExpressionChangedAfterItHasBeenCheckedError.

How to Fix This Error?

There are several ways to fix this issue, depending on the cause.

1. Use ChangeDetectorRef.detectChanges()

If the value is changing after the initial check, manually trigger change detection using ChangeDetectorRef:

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

@Component({
  selector: 'app-root',
  template: `<h1>{{ title }}</h1>`
})
export class AppComponent implements OnInit {
  title: string = 'Angular App';

  constructor(private cdRef: ChangeDetectorRef) {}

  ngOnInit() {
    setTimeout(() => {
      this.title = 'Updated Title';
      this.cdRef.detectChanges(); // βœ… Manually trigger change detection
    }, 0);
  }
}

βœ… This forces Angular to re-check the changes and update the DOM properly.


2. Use setTimeout() Correctly

setTimeout() moves the execution to a new event loop cycle, ensuring Angular doesn’t detect changes in the same cycle.

ngOnInit() {
  setTimeout(() => {
    this.title = 'Updated Title';
  });
}

βœ… This ensures the update happens in the next change detection cycle.


3. Use AfterViewInit Instead of OnInit

If you are modifying values that depend on the view (DOM elements, child components, etc.), use ngAfterViewInit instead of ngOnInit:

ngAfterViewInit() {
  this.title = 'Updated Title';
}

βœ… This ensures updates happen after the view has been initialized.

🚨 Caution: Do not modify properties bound to the template inside ngAfterViewInit, as it may still trigger the error. Use setTimeout() or ChangeDetectorRef instead.


4. Mark Component for Check Using markForCheck()

If your component uses OnPush change detection strategy, use markForCheck() to manually mark it as changed.

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

@Component({
  selector: 'app-root',
  template: `<h1>{{ title }}</h1>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
  title: string = 'Angular App';

  constructor(private cdRef: ChangeDetectorRef) {}

  ngOnInit() {
    setTimeout(() => {
      this.title = 'Updated Title';
      this.cdRef.markForCheck(); // βœ… Marks component for change detection
    }, 0);
  }
}

βœ… This marks the component for change detection in the next cycle.


5. Ensure Parent and Child Components Are in Sync

If the error occurs due to a child component updating a property that the parent is bound to, use ngOnChanges() in the child:

@Input() data: string;

ngOnChanges() {
  console.log('Data changed:', this.data);
}

βœ… This ensures Angular correctly tracks changes between parent and child components.


Best Practices to Avoid This Error

βœ”οΈ Do not modify component properties inside ngOnInit() asynchronously.
βœ”οΈ Use setTimeout() or Promise.resolve() to defer execution.
βœ”οΈ Use ChangeDetectorRef.detectChanges() if needed.
βœ”οΈ Use AfterViewInit instead of OnInit for DOM-dependent updates.
βœ”οΈ Ensure parent-child bindings are handled properly using ngOnChanges().
βœ”οΈ Use ChangeDetectionStrategy.OnPush with markForCheck() for performance optimization.


Leave a Reply

Your email address will not be published. Required fields are marked *