Angular Change Detection Strategy


Last Updated: 10/7/2025

Angular employs a mechanism called Change Detection to synchronize the application's data model with its view (DOM). This ensures that any changes in the component's state are reflected in the user interface.

Strategies

Angular offers two primary change detection strategies:

  • Default Strategy
  • OnPush Strategy

Default

  • This is the default behavior for all Angular components unless explicitly changed.
  • With this strategy, Angular performs a full check of the entire component tree, from the root down to the leaf components, during each change detection cycle.
  • A change detection cycle is typically triggered by various asynchronous events like:
    • DOM events (clicks, input changes, etc.)
    • HTTP requests (AJAX)
    • Timers (setTimeout, setInterval)
  • During the check, Angular compares the current values of template expressions and data bindings with their previous values. If any changes are detected, the corresponding parts of the DOM are updated.
  • While ensuring consistency, this strategy can be less performant in large applications with many components, as it checks every component even if only a small part of the application state has changed.

OnPush

  • This strategy offers a more optimized approach to change detection, focusing on performance.
  • To enable OnPush, you set the changeDetection property in the component's @Component decorator:
 @Component({
      selector: 'app-my-component',
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class MyComponentComponent {
      // ...
    }
  • When a component uses OnPush, Angular only checks for changes in that component (and its children) under specific conditions:
    • Input Property Changes: When an @Input() property's reference changes (meaning a new object or array is passed, not just a modification of an existing one).
    • Event Handling: When an event originating from the component or one of its children is handled.
    • Observable Emissions (with async pipe): When an Observable subscribed to using the async pipe emits a new value.
    • Explicit Marking: When ChangeDetectorRef.markForCheck() is called, explicitly marking the component as dirty for the next change detection cycle.
  • The OnPush strategy significantly reduces the number of checks, leading to improved performance, especially in applications with a deep component tree and frequently updated data. It encourages the use of immutable data structures to ensure that changes are detected efficiently.

How HttpClient/Async calls works in each strategy

Default

This code will work as expected

@Component({
  selector: 'app-change-detection',
  template: `<div>Todo Count: {{ todos.length }}</div>`
})
export class ChangeDetectionSample {
  todos: any[] = [];

  constructor(private httpClient: HttpClient) {}

  ngOnInit() {
    this.httpClient.get('https://jsonplaceholder.typicode.com/todos').
    subscribe((data: any) => {
      this.todos = data;
		});
  }
}

OnPush strategy

This code will not work as expected, because angular change detection will not run when data arrives

@Component({
  selector: 'app-change-detection',
  template: `<div>Todo Count: {{ todos.length }}</div>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChangeDetectionSample {
  todos: any[] = [];

  constructor(private httpClient: HttpClient) {}

  ngOnInit() {
    this.httpClient.get('https://jsonplaceholder.typicode.com/todos').
    subscribe((data: any) => {
      this.todos = data;
    });
  }
}

To make this code work, you can use

  • explicitly marking
  • async pipe
  • signal

Using Explicit Marking

Update constructor to inject ChangeDetectorRef

constructor(private httpClient: HttpClient, private cdr: ChangeDetectorRef) {}

Call markForCheck or detectChanges()

ngOnInit() {
    this.httpClient.get('https://jsonplaceholder.typicode.com/todos').
    subscribe((data: any) => {
      this.todos = data;
      this.cdr.markForCheck();
    });
  }

Using async pipe

Change declaration

todos$?: Observable<any[]>;

Update ngOnInit

this.todos$ = this.httpClient.get<any[]>('https://jsonplaceholder.typicode.com/todos');

In Template, use async pipe

template: `<div>Todo Count: {{ (todos$ | async)?.length }}</div>`

Using signal

todos: signal([])
ngOnInit() {
    this.httpClient.get('https://jsonplaceholder.typicode.com/todos').
    subscribe((data: any) => {
      this.todos.set(data);
    });
  }