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.

By default, Angular applies the ChangeDetectionStrategy.Default setting. With this approach:

  • Every component in the application is checked during each change detection cycle.
  • Any event, no matter how small, triggers a full check across the entire component tree.

This default behavior is simple and reliable, which makes it great for small or medium-sized applications. However, in large applications with complex user interfaces—such as enterprise apps with many nested components, dashboards, and real-time data streams—it can become inefficient. Running change detection everywhere for every event can slow down performance, leading to laggy interactions and wasted processing power.

That’s why developers often look for ways to optimize change detection, so Angular only updates the parts of the app that actually need 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

FeatureDefault (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
Set up the changeDetection property in your component decorator to activate the OnPush approach. This improves speed by preventing needless checks by instructing Angular to execute change detection for the component only in response to particular triggers (such as input reference changes, events, or observable emissions).

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.