Angular Change Detection: How It Works and How to Optimise It

Hero image for Angular Change Detection: How It Works and How to Optimise It. Image by Adrien Olichon.
Hero image for 'Angular Change Detection: How It Works and How to Optimise It.' Image by Adrien Olichon.

Change detection sits at the heart of Angular. It is the mechanism that keeps the DOM aligned with application state, which means performance conversations in Angular almost always end up here eventually.

That does not mean change detection is automatically a problem. For most applications it is fast enough. The real trouble tends to begin when we optimise without understanding it, or when we build large component trees and assume Angular will somehow guess what work is safe to skip.

In a product like Dataffirm, where an Angular interface is dealing with live analytics and dataheavy views, the real question is not whether Angular is "slow" in the abstract but where unnecessary checks and expensive rendering begin to stack up.


What Change Detection is Doing

At a high level, Angular checks components to ensure the latest state is reflected in the view. By default, this process can walk the component tree broadly, which is fine until the application becomes large enough that unnecessary checks start to matter.

This is why optimisation should begin with understanding rather than ritual. If we do not know when Angular checks a subtree, we cannot improve it sensibly.


What Actually Triggers a Check

In practice, Angular reacts to things such as events, async work observed through its runtime, input changes, and state changes that reach a component boundary. That is the part people often flatten into "Angular just runs change detection all the time", which is not a very useful mental model.

The better way to think about it is that Angular needs signals from the application runtime that something may have changed. Once that happens, the framework decides what it needs to revisit.

That distinction matters because performance work becomes much clearer when we can name what is causing checks rather than simply noticing that checks are happening.


OnPush Changes the Contract

The OnPush strategy tells Angular to skip a component subtree unless something meaningful happens, such as new input arriving or an event being handled in that part of the tree.

import { ChangeDetectionStrategy, Component, input } from '@angular/core';@Component({  selector: 'app-summary-card',  standalone: true,  template: `<p>{{ title() }}</p>`,  changeDetection: ChangeDetectionStrategy.OnPush,})export class SummaryCardComponent {  title = input.required<string>();}

This is powerful because it makes rendering behaviour more deliberate. We are no longer asking Angular to keep rechecking the subtree just in case.

It is also where immutability starts paying dividends. If inputs change by reference when the underlying value changes, OnPush becomes much easier to reason about.


Signals Make This Story More Interesting

Signals fit naturally with change detection because Angular can track where a signal is read. When a signal changes, Angular can mark the right places for update more precisely.

That does not mean signals eliminate change detection. They improve how state changes are tracked and propagated, which can reduce unnecessary work when used well.

More importantly, they make dependency flow more legible. Once state reads are clearer, rendering behaviour tends to become clearer too.


Templates Can Still Be the Real Cost

One easy mistake is focusing entirely on the detection mechanism and ignoring the work being done during render. If a template calls expensive functions repeatedly, or if derived state is recomputed in several places, the component can stay sluggish even if the change detection strategy is technically more selective.

That is why optimisation usually lands better when we address both sides:

  • reduce unnecessary checks
  • reduce the cost of each check that still happens

Practical Optimisation Tactics

  • use OnPush on components that benefit from stable input contracts
  • favour immutable updates so input reference changes remain meaningful
  • keep expensive computation out of templates
  • derive state once, close to the component or store, rather than repeatedly during rendering
  • profile before and after changing strategy

These choices improve maintainability too. A component designed around explicit inputs and stable derived state is usually easier to test and easier to scale.


Where People Get Caught Out

It can sound as if OnPush means Angular will never check the component again. That is not how it works. It narrows the circumstances in which Angular rechecks the subtree.

It can also seem as if change detection optimisation should come before profiling. In reality, premature change detection tuning can make code more complicated without producing a measurable gain.

Another trap is reaching for manual change detection APIs too quickly. If we constantly need escape hatches, the component boundary or state model may be the thing that needs revisiting.


When to Reach for Manual Intervention

There are edge cases where ChangeDetectorRef, markForCheck(), or similar techniques are appropriate. The key is restraint. If we need them constantly, the architecture may be fighting Angular rather than working with it.

Used sparingly, they can solve specific integration problems. Used habitually, they are often a sign that the component contract is too muddy.

The Angular documentation covers the API details behind these ideas particularly well:


Wrapping up

Angular change detection is most manageable when we stop treating it as invisible magic. Once we understand when checks happen, use OnPush where it genuinely helps, and model state carefully, performance work becomes much more grounded. That leads to components that are not only faster, but also easier to maintain and reason about.

Key Takeaways

  • Default change detection is often fine until scale makes unnecessary checks costly.
  • OnPush narrows rechecks by creating a clearer rendering contract.
  • Signals complement change detection by making state usage more explicit.
  • Template work still matters, not just the number of checks.

If we optimise with that mental model in place, Angular performance work becomes clearer and far less prone to accidental complexity.


Categories:

  1. Angular
  2. Development
  3. Front‑End Development
  4. Guides
  5. JavaScript
  6. Performance