One of the most crucial aspects of contemporary online apps is performance. The way the framework manages updates becomes crucial as Angular projects grow in size and complexity, particularly large enterprise apps with dashboards, forms, and real-time data. Effective change detection guarantees that the application remains responsive and quick. Slower screens and a bad user experience might result from poorly handled delays, even minor ones. For Angular apps to remain dependable and seamless, change detection optimization is essential.

Comprehending Angular Change Detection
To maintain the user interface (DOM) in sync with the application's data, Angular employs a mechanism known as Change Detection. Angular uses change detection to determine whether the view needs to be updated whenever something in the application changes, such as a user clicking a button, getting an HTTP response, or a timer going off.
- Angular uses the ChangeDetectionStrategy by default.default configuration. Using this method:
- During each cycle of change detection, every component in the application is examined.
- Every event, regardless of size, initiates a comprehensive check throughout the whole component tree.
This default behavior works well for small and medium-sized applications since it is straightforward and dependable. However, it can become ineffective in large systems with intricate user interfaces, including enterprise programs with numerous nested components, dashboards, and real-time data streams. Performance can be slowed down by running change detection everywhere for every event, which results in sluggish interactions and wasted processing resources.
Because of this, developers frequently search for ways to optimize change detection so that Angular only modifies the areas of the application that truly require it.
OnPush Change Detection Strategy
When you switch to ChangeDetectionStrategy.OnPush, Angular becomes more selective. Instead of checking every component on every cycle, Angular re-evaluates a component only when specific conditions are met:
- Input reference changes – Angular checks the component if one of its @Input() properties receives a new reference (not just a mutated object).
- Events triggered within the component – If a user interaction (like a click) occurs in the component or one of its children, Angular runs change detection for that subtree.
- Observable or Signal updates – When data streams (via Observable, async pipe) or Angular Signals emit new values, the component updates accordingly.
- Manual triggering using ChangeDetectorRef –
- markForCheck() tells Angular to include the component in the next change detection cycle.
- detectChanges() immediately runs change detection for the component and its subtree.
Default vs OnPush – Key Differences
| Feature | Default (Eager) | OnPush |
|
Change detection trigger
|
Any event
|
Input reference change
|
|
Performance
|
Moderate
|
High
|
|
Suitable for
|
Small apps
|
Large & scalable apps
|
|
Manual control
|
Rarely needed
|
Often needed
|
How to Use OnPush?
To enable the OnPush strategy, configure the changeDetection property inside your component decorator. This tells Angular to run change detection for the component only when specific triggers occur (such as input reference changes, events, or observable emissions), improving performance by avoiding unnecessary checks.
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'app-user-card',
templateUrl: './user-card.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserCardComponent {
@Input() user!: User;
}
With this setup, Angular will skip checking UserCardComponent unless its @Input() reference changes or another valid OnPush trigger occurs.
Why OnPush Improves Performance?
Angular’s default change detection strategy is ChangeDetectionStrategy.Default. With this approach:
- Angular traverses the entire component tree during every change detection cycle.
- It checks all bindings, even if the underlying data hasn’t changed.
- Any event—such as a click, an HTTP response, or a timer—triggers change detection across the whole application.
This works fine for smaller apps, but in large-scale applications with complex UIs, it can quickly become inefficient. The repeated checks consume unnecessary CPU cycles and lead to more DOM updates than needed, which can slow down performance.
By contrast, ChangeDetectionStrategy.OnPush changes the way Angular decides when to update a component:
- Angular skips checking a component unless its input properties change or an observable emits new data.
- This reduces the number of components Angular needs to process, lowering CPU usage.
- It minimizes DOM updates, ensuring only the parts of the UI that actually need refreshing are updated.
- As a result, applications become more scalable and responsive, even under heavy workloads.
OnPush is especially beneficial in scenarios such as:
- Data-heavy dashboards with multiple widgets and charts.
- Large tables with thousands of rows where frequent updates would otherwise be costly.
- Real-time applications that continuously receive new data streams.
- Enterprise Angular apps with deeply nested component structures and complex state management.
Important Concept: Immutability
OnPush works best when you use immutable data. This means you don’t directly change existing objects; instead, you create a new one when something changes.
Wrong Way (Mutation)
// Mutating the existing object
this.user.name = 'John';
Angular will not detect this change because the object reference remains the same.
Correct Way (New Reference)
// Creating a new object reference
this.user = {
...this.user,
name: 'John'
};
Here, Angular detects the change because the object reference is new. By following immutability, Angular’s OnPush strategy can correctly identify updates, making apps more predictable, easier to debug, and more efficient—especially in large-scale applications.
Using OnPush with Observables
When working with OnPush change detection, one of the best practices is to use the async pipe in your templates.
<!-- Using async pipe with OnPush -->
<div *ngIf="user$ | async as user">
{{ user.name }}
</div>
Why this works well:
- The async pipe automatically subscribes to the observable and unsubscribes when the component is destroyed, preventing memory leaks.
- It marks the component for check whenever new data is emitted, ensuring Angular updates the view correctly under OnPush.
- It keeps the code clean and declarative, avoiding manual subscription management in the component class.
- It fits perfectly with immutable data patterns, since each new emission creates a fresh reference that OnPush can detect.
Manual Change Detection (Advanced)
Even with OnPush, there are scenarios where Angular will not automatically detect changes—especially when updates occur outside Angular’s normal triggers (e.g., setTimeout, third-party libraries, manual DOM events, or non-reactive data updates).
In such cases, you can take manual control using ChangeDetectorRef.
Using markForCheck()
markForCheck() tells Angular: " This component should be checked in the next change detection cycle. "
It does not trigger detection immediately. Instead, it schedules the component to be checked when Angular runs the next cycle.
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
@Component({
selector: 'app-user-card',
template: `{{ data }}`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserCardComponent {
data = '';
constructor(private cd: ChangeDetectorRef) {}
updateData(newValue: string) {
this.data = newValue;
this.cd.markForCheck(); // Schedule check for next cycle
}
}
Best used when:
- Updating state asynchronously
- Integrating with third-party libraries
- Updating data outside Angular’s zone
Using detectChanges()
detectChanges() runs change detection immediately for the component and its subtree.
this.cd.detectChanges(); // Immediately updates the view
This is more aggressive because it forces Angular to re-evaluate the component instantly.
Best used when:
- You need immediate UI refresh
- You're inside callbacks where Angular won't trigger detection
- You detached change detection and want to manually control execution
Overusing markForCheck() or detectChanges() can defeat the purpose of OnPush by reintroducing frequent checks.
Manual change detection should be:
- A targeted solution, not a default pattern
- Used only when reactive patterns (Observables, Signals, immutable inputs) are not sufficient
Conclusion
The strategy for change detection.One of Angular's best performance-enhancing technologies is OnPush. It encourages an immutable architecture, enhances scalability, and minimizes pointless tests. Additionally, OnPush easily integrates with Signals and Observables, increasing the effectiveness of reactive patterns. Gaining proficiency with OnPush is essential for developers constructing enterprise-grade applications in order to produce Angular apps that are quick, responsive, and manageable.