How to Use ChangeDetectionStrategy.OnPush in Angular for Performance‑Critical Components
Learn how to enable Angular’s ChangeDetectionStrategy.OnPush for faster components, with a step‑by‑step example, testing tips, and common pitfalls to avoid.
09 Feb 2026, 06:28 UTC

Why OnPush Matters
Angular’s default change detection runs a deep tree walk every time the framework schedules a view update. In an application with dozens of components, that can become a performance bottleneck. ChangeDetectionStrategy.OnPush tells Angular to skip the component’s subtree unless one of three conditions is met:
- an
@Inputreference changes - an event originates inside the component
- the component calls
markForCheck()(ordetectChanges())
When you design your components around immutable data or rely on the async pipe, OnPush can dramatically reduce the number of change‑detection cycles, leading to smoother UIs and lower CPU usage.
Enabling OnPush in a Component
Below is a minimal example that demonstrates the configuration and a typical usage pattern. The component receives a user object via @Input and renders the name and email. The parent component updates the user with a new reference only when the data actually changes.
// user-card.component.ts
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
export interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-card',
template: `
{{ user?.name }}
{{ user?.email }}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserCardComponent {
@Input() user!: User;
}
Key points:
- The
changeDetectionproperty is set toChangeDetectionStrategy.OnPush. - Inputs are expected to be immutable; the parent should create a new
Userobject when the data changes. - Because the component’s template uses only the
userreference, Angular will skip this subtree unlessuserchanges.
Parent Component: Providing Immutable Inputs
Here’s how the parent can supply a new reference only when necessary. The example uses RxJS to simulate a data stream and the async pipe to automatically trigger change detection when the observable emits a new value.
// user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { User } from '../user-card/user-card.component';
@Component({
selector: 'app-user-list',
template: `
`,
})
export class UserListComponent implements OnInit {
users$!: Observable;
ngOnInit() {
// Simulate fetching users; each emission returns a new array reference.
this.users$ = of([
{ id: 1, name: 'Alice', email: '[contact removed]' },
{ id: 2, name: 'Bob', email: '[contact removed]' },
]).pipe(delay(0));
}
}
Because the async pipe subscribes to users$ and pushes a new array reference to the app-user-card components, those components will run change detection only when the array changes. If the observable emits the same array instance, OnPush will skip the subtree entirely.
Testing That OnPush Works
To confirm the strategy is active, you can use Angular DevTools or Chrome DevTools’ Performance tab. Here’s a quick checklist:
- Open the component in the browser.
- In Angular DevTools, locate the component tree and verify the
ChangeDetectioncolumn showsOnPush. - Trigger a parent change that creates a new
Userreference (e.g., by clicking a button that calls a method returning a new object). - Observe that only the
app-user-cardcomponent for the updated user runs change detection. - Try mutating the existing
Userobject without changing its reference; the component should not update, confirming that OnPush relies on reference changes.
Remember that OnPush does not eliminate change detection entirely; events inside the component (e.g., button clicks) or manual markForCheck() calls will still trigger an update.
Common Pitfalls
- Mutable Inputs: If you mutate an object in place, Angular cannot detect the change because the reference stays the same. Always create a new object when updating data.
- Forgetting the Strategy: Adding
changeDetectionto the component decorator is mandatory; otherwise Angular falls back to the defaultChangeDetectionStrategy.Default. - Using
asyncwith Mutable Streams: If a stream emits the same array instance, the UI will not refresh. Ensure the stream emits a new reference when data changes. - Assuming OnPush Eliminates All Overhead: OnPush only skips the component’s subtree; other parts of the app still run change detection. It’s a tool, not a silver bullet.
- Not Marking for Check on External Triggers: If data changes outside Angular’s zone (e.g., from a WebSocket or a third‑party library), you must call
ChangeDetectorRef.markForCheck()to inform Angular of the update.
When Not to Use OnPush
If a component receives frequent mutable updates—such as a live data feed that mutates an array in place—OnPush can cause stale views. In such cases, the default strategy or a more granular markForCheck() approach may be preferable.
Practical Checklist Before Enabling OnPush
| Item | Action |
|---|---|
| All @Input data is immutable | Use spread operators or libraries like Immer to create new objects |
| Async streams emit new references | Map or pipe to return new arrays/objects on each emission |
| Component events are handled internally | Ensure event handlers are defined in the component and not passed as callbacks |
| External updates are marked | Inject ChangeDetectorRef and call markForCheck() when needed |
Conclusion
Enabling ChangeDetectionStrategy.OnPush is a straightforward way to improve performance in Angular applications that can exploit immutable data patterns. By configuring the component, ensuring inputs are immutable, and verifying behavior with dev tools, you can confidently reduce unnecessary change‑detection cycles and deliver a snappier user experience.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.