Skip to content

Change Detection

Change detection is the mechanism that Angular uses to update the view when the state of the application changes. Angular provides two main strategies for change detection: ChangeDetectionStrategy.Default (Zone.js) and ChangeDetectionStrategy.OnPush.

Angular’s change detection cycle is synchronous what means that it will take time to go from the root component to the leaf components in the component tree.

Zone.js monkey patches all async browser API’s and notifies Angular. When an event occurs Zone.js triggers Change Detection and all component’s templates are re-evaluated to determine what has changed in the DOM.

  • Outer Zone: ALso known as the Parent Zone, and it will never trigger Change Detection, it can be used with NgZone.runOutsideAngular()
  • Inner Zone: Also known as the Child Zone, it’s the default zone and it will always trigger Change Detection, it can be used with NgZone.run()

We can run browser events like setInterval, setTimeout, AddEventListener, outside Angular’s zone to avoid unnecessary change detection cycles.

timer.component.ts
import { Component, NgZone } from "@angular/core";
@Component({
selector: "app-timer",
standalone: true,
imports: [],
templateUrl: "./timer.component.html",
styleUrl: "./timer.component.css",
})
export class TimerComponent {
constructor(private zone: NgZone) {
// Avoiding zone pollution
this.zone.runOutsideAngular(() => {
setInterval(() => {
console.log("Timer tick");
}, 1000);
});
}
}

Using browser events like setInterval, setTimeout, AddEventListener, inside Angular’s zone will always trigger change detection.

When using the default change detection strategy, Angular will check all components in the component tree for changes whenever an event occurs.

With ChangeDetectionStrategy.OnPush Change Detection never runs unless the view is dirty.

When a change occurs only the component which has changed and its children are re-evaluated to determine what has changed in the DOM.

Change detection OnPush it can be triggered by:

  • An @Input() property changes.
  • An event originated from the component or one of its children (event binding in the template).
  • async pipes (| async). Internally calls markForCheck().
  • Manually triggering change detection (changeDetectorRef.markForCheck()).
  • A Signal used in the template changes.
on-push.component.ts
import { Component, ChangeDetectionStrategy } from "@angular/core";
@Component({
selector: "app-on-push",
standalone: true,
imports: [],
templateUrl: "./on-push.component.html",
styleUrl: "./on-push.component.css",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OnPushComponent {
count = 0;
increment() {
this.count++;
}
}

markForCheck() marks the component and its ancestors as dirty, so they will be checked in the next change detection cycle.

detectChanges() triggers change detection for the component and its children immediately.