🅰️ Angular Q55 / 125

How does OnPush change detection strategy work?

AI-Powered Answer ✓ Answered

Angular's change detection mechanism is crucial for updating the UI in response to data changes. By default, Angular checks all components in the component tree after every asynchronous event. The OnPush change detection strategy offers a performance optimization by allowing components to be checked only when specific conditions are met, leading to more efficient application rendering.

What is Change Detection?

Change detection is the process by which Angular determines if an application's state has changed and, if so, re-renders the UI to reflect those changes. It's a fundamental part of how Angular keeps your view synchronized with your data model.

Default Change Detection Strategy

By default, Angular uses a change detection strategy that automatically checks all components from top to bottom every time an event occurs in the application. This includes browser events (clicks, keypresses), HTTP requests, timers (setTimeout, setInterval), and other asynchronous operations. While robust, this can become inefficient in large applications with many components.

Introducing OnPush Strategy

The OnPush change detection strategy, applied at the component level using changeDetection: ChangeDetectionStrategy.OnPush, tells Angular to run change detection for that component and its children only when one of the following conditions is met. This significantly reduces the number of checks performed, improving application performance.

Conditions for OnPush Change Detection

  • Any of the component's @Input() properties change their reference (i.e., a new object or primitive value is passed, not just a mutation of an existing object).
  • An event originated from the component itself or one of its children (e.g., a click handler, a (keyup) event).
  • An observable bound to the component's template using the async pipe emits a new value.
  • The ChangeDetectorRef.detectChanges() method is explicitly called on the component's change detector.
  • The ChangeDetectorRef.markForCheck() method is explicitly called on the component's change detector (this marks the component as dirty and schedules a check during the next change detection cycle, but doesn't trigger it immediately).

Example: Input Reference Change

When using OnPush, if an input property is an object, mutating its properties will not trigger change detection. A new object reference must be passed for the component to re-render.

typescript
import { Component, Input, ChangeDetectionStrategy, OnChanges, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
    <p>Child Value: {{ data.value }}</p>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnChanges {
  @Input() data: { value: string };

  ngOnChanges(changes: SimpleChanges): void {
    console.log('Child ngOnChanges:', changes);
  }
}

// In Parent Component:
// Will NOT trigger change detection in child:
// this.data.value = 'new value';

// WILL trigger change detection in child:
// this.data = { ...this.data, value: 'new value' };
// Or:
// this.data = { value: 'completely new object' };

Using `markForCheck()`

Sometimes, you might mutate an input object or trigger an asynchronous operation that doesn't fall under the standard OnPush triggers. In such cases, you can explicitly tell Angular to check the component by injecting ChangeDetectorRef and calling markForCheck().

typescript
import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';

@Component({
  selector: 'app-mutating-child',
  template: `
    <p>Mutated Value: {{ item.name }}</p>
    <button (click)="updateItem()">Update Item (Internal)</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MutatingChildComponent {
  @Input() item: { name: string };

  constructor(private cdr: ChangeDetectorRef) {}

  updateItem() {
    // Mutating the object directly will not trigger change detection
    this.item.name = 'Updated Internally ' + Math.random().toFixed(2);
    console.log('Item mutated:', this.item);

    // Explicitly mark for check to ensure UI updates
    this.cdr.markForCheck();
  }
}

Benefits of OnPush

  • Improved Performance: By checking fewer components, especially in complex UIs, rendering performance is significantly enhanced.
  • Predictable Change Detection: Developers gain a clearer understanding of when and why components are being checked, leading to more predictable application behavior.
  • Encourages Immutability: OnPush naturally promotes the use of immutable data structures, which can simplify state management and prevent common bugs related to unexpected data mutations.
  • Reduced Bundle Size (Potentially): While not directly a bundle size reduction, it reduces the need for Zone.js patching for components configured with OnPush if combined with NoopZone strategy (though this is a more advanced topic).

When to use OnPush

It is generally recommended to use OnPush change detection strategy for most components in an Angular application, particularly for presentational or 'dumb' components that primarily receive data via @Input and emit events via @Output. Adopting OnPush from the start helps build a more performant and maintainable application, even if the immediate performance gains aren't dramatically noticeable in smaller apps.

TriggerDefault StrategyOnPush Strategy
@Input() property mutationChecks alwaysDoes NOT check
@Input() property reference changeChecks alwaysChecks
Event from component/childChecks alwaysChecks
Async pipe emits valueChecks alwaysChecks
Manual `detectChanges()` / `markForCheck()`Checks always (if applicable)Checks