Skip to content

Angular Change Detection

Change detection is the mechanism Angular uses to notice when component state has changed and update the DOM accordingly. This lesson explains how it works, and how it's evolving with signals.

What Is Change Detection?

Change detection is Angular's process of checking every component's bindings to see if anything changed since the last check, and if so, updating the corresponding parts of the DOM. Traditionally, this check runs across the whole component tree whenever something might have changed.

Angular knows *when* to run this check thanks to Zone.js, a library that patches common async APIs (setTimeout, event listeners, Promises, HTTP responses) so Angular is automatically notified whenever one of them fires.

@Component({ selector: 'app-counter', standalone: true, template: '<button (click)="increment()">{{ count }}</button>' })
export class CounterComponent {
  count = 0;
  increment() {
    this.count++; // Zone.js notices the click event finished, triggers change detection
  }
}

You never call anything like this.render() yourself, Zone.js and Angular's change detector handle noticing the click and updating the button's text automatically.

How a Change Detection Cycle Runs

Async event occurs (click, setTimeout, HTTP response, ...)
        |
        v
Zone.js notifies Angular
        |
        v
Angular checks bindings from the root component down
        |
        v
DOM updated wherever a binding's value actually changed
  • Zone.js patches async browser APIs so Angular knows when "something might have changed".
  • By default, Angular checks the entire component tree top-down on every such event.
  • Only bindings whose value actually changed cause a DOM update, unnecessary DOM writes are avoided.
  • Signals provide a newer mechanism that can skip the "check everything" step for signal-driven state.

Change Detection Cheatsheet

Key concepts and APIs related to Angular's change detection system.

Concept Role
Zone.js Notifies Angular when an async operation completes
Default change detection Checks the whole component tree on every notified event
OnPush strategy Skips checking a component unless its inputs/signals actually changed
ChangeDetectorRef.markForCheck() Manually flags a component for the next check
ChangeDetectorRef.detectChanges() Manually runs change detection immediately for a component
Signals Enable fine-grained updates that can bypass zone-based checking for signal reads
Zoneless Angular (experimental) Removes Zone.js, relying on signals for change notification

The Role of Zone.js

Zone.js works by monkey-patching common asynchronous browser APIs so that, whenever one of them fires (a DOM event, a timer, an HTTP response, a resolved Promise), it wraps the callback in a way that lets Angular know afterward: "something happened, you should check for changes."

Default Change Detection vs OnPush (Preview)

The default change detection strategy checks every component in the tree, even ones with no actual changes, which is simple but can be wasteful in large component trees. The OnPush strategy (covered in depth in the next lesson) tells Angular to skip checking a component unless one of its inputs changed by reference, or a signal it reads was updated.

How Signals Change the Picture

Because a signal explicitly tracks every place it's read, Angular can, increasingly, update just the specific DOM bindings that depend on a changed signal, without needing to re-check the entire surrounding component tree the way zone-based change detection traditionally has.

  • Signals make dependency tracking explicit rather than inferred from "something async happened".
  • This enables more precise, fine-grained DOM updates over time as Angular's rendering engine evolves.
  • Zoneless Angular (an experimental, opt-in mode) removes Zone.js entirely, relying on signals and explicit notifications instead.

Manually Controlling Change Detection

In rare cases (working with a third-party library that mutates data outside Angular's knowledge, for example), ChangeDetectorRef lets you manually trigger or schedule a check.

export class ThirdPartyWidgetComponent {
  private cdr = inject(ChangeDetectorRef);

  onExternalLibraryUpdate(value: string) {
    this.value = value;
    this.cdr.markForCheck(); // schedule a check on next cycle
  }
}

Common Mistakes

  • Mutating data outside of Angular's zone (in a raw DOM callback from a third-party library) and expecting the view to update automatically.
  • Overusing detectChanges()/markForCheck() as a workaround instead of understanding why a view isn't updating.
  • Assuming change detection only runs once per user action, in reality it can run many times per interaction as different async operations resolve.
  • Ignoring performance implications of a very large default-strategy component tree with expensive bindings.

Key Takeaways

  • Change detection is how Angular notices state changes and updates the DOM accordingly.
  • Zone.js patches async APIs to notify Angular when a check might be needed.
  • Default change detection checks the whole component tree; OnPush and signals enable more targeted checking.
  • Signals are pushing Angular toward more fine-grained, potentially zoneless change detection over time.

Pro Tip

You rarely need to think about Zone.js directly in day-to-day development, focus instead on using signals and the OnPush strategy where appropriate, they give you the performance benefits without needing to reason about the underlying mechanism manually.