The error “Expression changed after digest cycle” occurs when Angular detects that a binding value has changed after the initial change detection cycle has completed. This typically happens due to updates triggered asynchronously or inside Angular’s lifecycle hooks.
Step 1: What Causes This Error?
1. Updating a Model Inside a Lifecycle Hook
If you update a bound variable inside Angular’s lifecycle hooks like ngOnInit()
(Angular) or $digest()
(AngularJS), Angular may detect a second change after the initial one.
Example (Angular)
export class AppComponent {
message: string = 'Hello';
ngOnInit() {
this.message = 'Updated Message'; // This will cause an error
}
}
Here, message
is changed after the first digest cycle, leading to an error.
Solution: Use setTimeout()
ngOnInit() {
setTimeout(() => {
this.message = 'Updated Message';
});
}
This moves the update to the next cycle, preventing the error.
2. Asynchronous Updates (Promises, Observables, setTimeout)
If data updates asynchronously but outside Angular’s change detection cycle, it can cause this error.
Example (Promise)
ngOnInit() {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
this.message = data.text; // This may cause an error
});
}
The API updates message
after the first cycle, triggering an error.
Solution: Use ChangeDetectorRef
Inject ChangeDetectorRef
and use detectChanges()
:
import { ChangeDetectorRef } from '@angular/core';
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
this.message = data.text;
this.cd.detectChanges(); // Forces Angular to detect changes safely
});
}
3. Direct DOM Manipulation
If you manually update the DOM (e.g., document.querySelector()
), Angular won’t track the change, causing this error.
Example
document.getElementById('title')!.innerText = 'New Title';
Solution: Use Angular Data Binding
Instead of modifying the DOM manually, bind the value in HTML:
<h1>{{ title }}</h1>
4. Using ngModel
or Form Controls Inside Lifecycle Hooks
If you update a form control inside ngOnInit()
, it might trigger the error.
Example
this.form.controls['username'].setValue('newUser'); // Causes error
Solution: Use setTimeout()
setTimeout(() => {
this.form.controls['username'].setValue('newUser');
});
5. Updating Data Inside a Child Component
If a child component updates a parent component’s data, Angular detects multiple changes.
Example
@Input() message!: string;
ngOnChanges() {
this.message = 'Updated by Child';
}
Solution: Use ngAfterViewInit()
Instead
ngAfterViewInit() {
setTimeout(() => {
this.message = 'Updated by Child';
});
}
Step 2: Debugging Steps
- Check for Updates in Lifecycle Hooks (
ngOnInit()
,$digest()
). - Ensure Asynchronous Updates (
setTimeout()
,Promise
,Observable
) Are Handled Properly. - Use
ChangeDetectorRef.detectChanges()
When Necessary. - Avoid Direct DOM Manipulation (
document.querySelector()
). - Use
ngAfterViewInit()
Instead ofngOnInit()
for UI Updates.