Question
In Angular, why does this component throw an ExpressionChangedAfterItHasBeenCheckedError even though it only updates a simple bound property after the view is initialized?
@Component({
selector: 'my-app',
template: `<div>I'm {{ message }}</div>`,
})
export class App {
message: string = 'loading :(';
ngAfterViewInit() {
this.updateMessage();
}
updateMessage() {
this.message = 'all done loading :)';
}
}
The error is:
EXCEPTION: Expression 'I\'m {{message}} in App@0:5' has changed after it was checked.
Previous value: 'I\'m loading :('.
Current value: 'I\'m all done loading :)'
Why does Angular complain here, and what is the correct way to think about this lifecycle behavior?
Short Answer
By the end of this page, you will understand why Angular throws ExpressionChangedAfterItHasBeenCheckedError, how change detection interacts with lifecycle hooks like ngAfterViewInit, and how to update component state safely without confusing Angular’s view consistency checks.
Concept
ExpressionChangedAfterItHasBeenCheckedError happens when Angular detects that a value used in the template changed after Angular already checked it during the same change detection cycle.
In the example, message starts as:
'loading :('
Angular renders and checks the template:
<div>I'm {{ message }}</div>
Then ngAfterViewInit() runs, and inside it you change message to:
'all done loading :)'
That means Angular just finished checking the view with one value, and then the value changed immediately afterward during the same cycle. In development mode, Angular performs an extra verification pass to catch exactly this kind of unstable state.
This error is useful because it warns you that the UI is not stable during rendering. If Angular allowed values to keep changing while a check was in progress, the DOM could become inconsistent or harder to reason about.
Why this matters
Angular’s rendering model expects template-bound values to be stable during a check. Lifecycle hooks are not all equally safe for changing data:
ngOnInit()is generally safe for initializing component state.ngAfterViewInit()is for reacting to the view being created.- Changing bound values in
ngAfterViewInit()can trigger this error because the view has already been checked.
In short:
- Angular checks the template
- Angular runs
ngAfterViewInit() - You change a bound value
- Angular notices the checked value is no longer the same
- Angular throws the error in development mode
Mental Model
Think of Angular change detection like a teacher collecting exam papers.
- First, the teacher reads the answer sheet.
- Then, just after the sheet is marked as checked, the student tries to erase an answer and write a new one.
- The teacher says: "Wait, this was already checked. Why did it change now?"
That is what Angular is doing.
ngAfterViewInit() happens after the view exists and has been checked. If you change a displayed value there, Angular sees that the already-checked output no longer matches the current state.
A good mental rule is:
- Before check: prepare values
- During/after view check: avoid changing values that the template already used
- If you must react after view init: schedule the update for a later turn or trigger change detection intentionally
Syntax and Examples
The key Angular lifecycle hooks involved here are:
ngOnInit() {
// initialize component state
}
ngAfterViewInit() {
// view is created and child views are available
}
Safe initialization in ngOnInit
If the value does not depend on the rendered view, initialize it earlier:
@Component({
selector: 'my-app',
template: `<div>I'm {{ message }}</div>`,
})
export class App {
message = 'loading :(';
ngOnInit() {
this.message = 'all done loading :)';
}
}
This works because Angular has not finished checking the view yet.
Delaying the update until the next task
If you truly need ngAfterViewInit(), you can defer the change:
@Component({
selector: 'my-app',
template: `<div>I'm {{ message }}</div>`,
})
export class App {
message = 'loading :(';
ngAfterViewInit() {
setTimeout(() => {
this.message = 'all done loading :)';
});
}
}
This moves the update to a later turn of the event loop, so it happens in a new change detection cycle.
Using ChangeDetectorRef
Another common approach is to update the value and then explicitly request a fresh check:
import { Component, AfterViewInit, ChangeDetectorRef } from '@angular/core';
@Component({
selector: 'my-app',
template: `<div>I'm {{ message }}</div>`,
})
export class App implements AfterViewInit {
message = 'loading :(';
constructor(private cdr: ChangeDetectorRef) {}
ngAfterViewInit() {
this.message = 'all done loading :)';
this.cdr.detectChanges();
}
}
Use this carefully. It is often better to rethink where the state change belongs.
What to prefer
- Use
ngOnInit()for initial data setup - Use
ngAfterViewInit()for view/query-dependent logic - Defer updates only when you truly need post-view behavior
Step by Step Execution
Consider this code:
@Component({
selector: 'my-app',
template: `<div>I'm {{ message }}</div>`,
})
export class App {
message = 'loading :(';
ngAfterViewInit() {
this.message = 'all done loading :)';
}
}
Here is what happens step by step in development mode:
-
Angular creates the component instance.
messageis'loading :('
-
Angular begins change detection.
- It evaluates the template expression
{{ message }} - The template result is
I'm loading :(
- It evaluates the template expression
-
Angular creates and initializes the view.
-
Angular runs
ngAfterViewInit().- Your code changes
messageto'all done loading :)'
- Your code changes
-
Angular performs its development-mode verification.
- It checks whether the template expressions are still the same as the checked values
- Previously checked value:
'loading :(' - Current value:
'all done loading :)'
-
Angular throws
ExpressionChangedAfterItHasBeenCheckedError.
Why only in development mode?
Angular dev mode runs extra checks to detect unstable rendering. In production mode, this specific error is not thrown, but the underlying timing issue still exists. That means you should fix the cause, not just ignore the error.
Trace with a safe version
ngOnInit() {
this.message = 'all done loading :)';
}
Now the order is:
- Component created
messageupdated before the view is finalized- Angular checks the template
- The checked value is already correct
- No error
Real World Use Cases
This issue commonly appears in real Angular apps when developers change template-bound values too late in the lifecycle.
Common scenarios
-
Loading indicators
- Setting
loading = falseinsidengAfterViewInit()after the template already showedtrue
- Setting
-
ViewChild-based setup
- Reading a child component or DOM element in
ngAfterViewInit()and then changing text, classes, or flags used in the template
- Reading a child component or DOM element in
-
Dynamic layout logic
- Measuring element size after render and then changing template-bound layout values immediately
-
Form setup
- Updating validation state or field flags after the view was already checked
-
Conditional rendering
- Toggling
*ngIfordisabledflags from post-view hooks without deferring the change
- Toggling
Example: spinner state
loading = true;
ngAfterViewInit() {
this.loading = false; // may cause the error
}
Better approaches:
- initialize earlier if possible
- update asynchronously if it depends on the finished view
- trigger controlled change detection when necessary
Real Codebase Usage
In real codebases, developers usually handle this problem by choosing the correct lifecycle hook or by structuring state updates more clearly.
Common patterns
1. Initialize state early
Use ngOnInit() for values that do not depend on the DOM or child views.
ngOnInit() {
this.isReady = true;
}
2. Use guard clauses around view-dependent logic
When ngAfterViewInit() is needed, keep it focused on view access:
ngAfterViewInit() {
if (!this.chartContainer) return;
this.initializeChart();
}
Then separate UI state updates carefully.
3. Defer view-dependent state changes
ngAfterViewInit() {
Promise.resolve().then(() => {
this.hasMeasuredLayout = true;
});
}
This is often used when the update must happen after the current check completes.
4. Explicit change detection for integration code
Libraries that manipulate the DOM or emit late callbacks may require manual change detection:
constructor(private cdr: ChangeDetectorRef) {}
ngAfterViewInit() {
this.widget.init();
this.ready = true;
this.cdr.detectChanges();
}
5. Validation and early return patterns
Developers often prevent unnecessary updates:
updateMessage(newMessage: string) {
if (this.message === newMessage) return;
this.message = newMessage;
}
This does not solve lifecycle timing by itself, but it reduces noisy state changes.
Practical guideline
If a value is displayed in the template, ask:
- Can I set it before the first render?
- Does it depend on a view query like
@ViewChild? - Should this update happen in a later async turn?
- Do I need
ChangeDetectorRef, or am I masking a design issue?
Common Mistakes
1. Changing bound values in ngAfterViewInit() without needing to
Broken example:
ngAfterViewInit() {
this.title = 'Ready';
}
If title does not depend on the view, move it to ngOnInit().
2. Using setTimeout() as the first solution every time
This works, but it can hide design problems.
ngAfterViewInit() {
setTimeout(() => {
this.title = 'Ready';
});
}
Use it only when the update genuinely must happen after the current cycle.
3. Calling detectChanges() everywhere
Broken style:
ngAfterViewInit() {
this.message = 'Done';
this.cdr.detectChanges();
}
This can be valid, but if overused it makes the app harder to reason about. Prefer better lifecycle placement first.
4. Assuming the error means Angular is broken
It is actually Angular protecting you from unstable state. The error means your component changed rendered data at the wrong moment.
5. Ignoring dev-mode-only errors
Some beginners think this can be ignored because production mode does not throw it. That is risky. The timing issue still exists and may cause confusing UI behavior.
6. Confusing ngOnInit() and ngAfterViewInit()
ngOnInit()= initialize component datangAfterViewInit()= interact with the fully created view
If you remember that distinction, many of these errors disappear.
Comparisons
| Concept | When it runs | Safe for normal state initialization? | Typical use |
|---|---|---|---|
constructor | When the class is created | Usually no for app logic | Dependency injection, basic setup |
ngOnInit | After Angular sets input properties | Yes | Initialize component data |
ngAfterViewInit | After the component view is created and checked | Often no for immediate bound-value changes | Access @ViewChild, DOM-dependent logic |
ngOnInit() vs ngAfterViewInit()
- Use
ngOnInit()when the value can be decided from inputs, services, or plain logic. - Use
ngAfterViewInit()when you need the rendered view or child components to exist first.
Synchronous update vs deferred update
| Approach | What it does | Good for | Trade-off |
|---|---|---|---|
Direct assignment in ngOnInit() | Updates state before view stability check | Initial state | Best option when possible |
Direct assignment in ngAfterViewInit() | Changes state after check | Rarely safe | Can trigger the error |
setTimeout / microtask | Delays update to next turn | Post-view adjustments | Can hide poor structure if overused |
detectChanges() | Forces another check | Integrations and special cases | Easy to misuse |
Cheat Sheet
Quick rules
ExpressionChangedAfterItHasBeenCheckedErrormeans a template-bound value changed after Angular checked it.- This usually appears in development mode.
- A common trigger is updating bound state in
ngAfterViewInit().
Safe choices
ngOnInit() {
this.value = 'ready';
}
Risky choice
ngAfterViewInit() {
this.value = 'ready'; // may throw
}
If post-view update is required
ngAfterViewInit() {
setTimeout(() => {
this.value = 'ready';
});
}
or
ngAfterViewInit() {
this.value = 'ready';
this.cdr.detectChanges();
}
Lifecycle reminder
constructor: class creationngOnInit: initialize datangAfterViewInit: view is ready
Best practice
- Prefer setting bound values before the first completed check
- Use post-view hooks for DOM/view logic, not general state initialization
- Treat the error as a useful signal, not just something to suppress
FAQ
Why does Angular throw ExpressionChangedAfterItHasBeenCheckedError?
Because a value used in the template changed after Angular had already checked that value in the current change detection cycle.
Why does this often happen in ngAfterViewInit()?
Because ngAfterViewInit() runs after the component view has been created and checked, so changing a bound property there can make the checked output immediately outdated.
Is this error only in development mode?
Usually yes. Angular dev mode performs an extra verification pass. Production mode does not throw the same error, but the timing issue still exists.
Should I always fix it with setTimeout()?
No. First ask whether the value should be set in ngOnInit() instead. Use deferring only when the update truly depends on the completed view.
When should I use ChangeDetectorRef.detectChanges()?
Use it when you genuinely need a manual refresh after view-dependent logic or external integration. It should not be the default fix for every case.
Is changing data in ngAfterViewInit() always wrong?
No. It is only a problem when the changed data is already bound in the template and Angular already checked it during that cycle.
What is the simplest fix for the example shown?
Move the message update to ngOnInit() if it does not depend on the view.
Mini Project
Description
Build a small Angular component that shows a loading message first and then updates to a ready message after the view is initialized. The project demonstrates both the incorrect approach that causes ExpressionChangedAfterItHasBeenCheckedError and a correct approach that defers the update safely.
Goal
Create a component that updates its message after view initialization without triggering Angular’s expression-changed error.
Requirements
- Create a component with a
messageproperty bound in the template. - Show an initial loading message.
- Update the message after the view is initialized.
- Avoid
ExpressionChangedAfterItHasBeenCheckedError. - Keep the example simple and traceable.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.