Going DeeperPro· 35 min read

Change Detection & Performance

Angular re-checks the screen when data might have changed — and OnPush, trackBy and the async pipe let you make that fast.

What you will learn

  • Explain what change detection is in plain words
  • Speed up a component with OnPush
  • Apply trackBy and the async pipe for smoother lists

How Angular keeps the screen correct

Change detection is Angular’s process of checking your data and updating the screen so what the user sees always matches your component’s values. After anything that might change data — a click, an HTTP reply, a timer — Angular walks your components and refreshes the bits that changed. For small apps this is invisibly fast; for big apps you sometimes want to do less of it.

OnPush: check this component less often

By default Angular re-checks every component on every event. The OnPush strategy tells Angular: “only re-check this component when its @Input changes, an event fires inside it, or an Observable it uses (via the async pipe) emits.” That can cut a huge amount of needless checking.

Switching a component to OnPush
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';

@Component({
  selector: 'app-task-card',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,   // check less often
  template: '<div>{{ task.title }}</div>'
})
export class TaskCardComponent {
  @Input() task!: { title: string };
}

Note: Output: (Same on screen — but checked far less often.) With OnPush, Angular skips re-checking this card unless its task input is replaced with a new object, an event happens inside it, or an async-pipe Observable emits. In a list of hundreds of cards, that is a big saving.

OnPush works best when you treat data as immutable — create a new object/array instead of editing the old one, so Angular notices the reference changed.

Replace data, don’t mutate it, with OnPush
// triggers OnPush update: a brand-new array
this.items = [...this.items, newItem];

// does NOT reliably update OnPush: mutating in place
this.items.push(newItem);

Note: Output: The top line updates an OnPush component; the bottom may not. Because OnPush compares references, a new array (top) is seen as “changed”, while pushing into the same array (bottom) keeps the same reference and can be missed. This is why immutability and OnPush go together.

trackBy: update only the rows that changed

When a list re-renders, Angular by default may rebuild every row. trackBy (or track in the new @for) gives each item a stable identity, so Angular reuses unchanged rows and touches only what actually changed.

trackBy keeps unchanged rows in place
<!-- old syntax -->
<li *ngFor="let user of users; trackBy: trackById">{{ user.name }}</li>
Return a stable id per item
// component method
trackById(index: number, user: { id: number }) {
  return user.id;     // identify each row by its id
}

Note: Output: (The list looks identical — but updates far less work.) Without trackBy, replacing the array rebuilds every <li>. With it, Angular matches rows by id and only re-renders the ones that truly changed — noticeably smoother for long, frequently-updated lists. In the new control flow you write @for (user of users; track user.id).

TechniqueWhat it saves
OnPushSkips re-checking components that did not change
trackBy / trackReuses list rows instead of rebuilding all
async pipeAuto subscribe/unsubscribe; plays well with OnPush
Immutable updatesLets OnPush detect changes reliably

Tip: Newer Angular also offers a zoneless mode, where signals drive updates directly instead of Angular guessing after every event. Signals + OnPush + async pipe are the path to fast, predictable apps.

Watch out: Do not scatter OnPush everywhere blindly. Add it where a component renders a lot or updates often, and make sure you update data immutably there — otherwise the screen can appear “stuck” because Angular never sees a change.

Q. With ChangeDetectionStrategy.OnPush, when does Angular re-check the component?

Answer: OnPush limits checks to when an input reference changes, an internal event occurs, or an Observable used via the async pipe emits — which is why immutable data updates matter.

✍️ Practice

  1. Add OnPush to a list-item component and update its data by replacing the array.
  2. Add trackBy (or track) to a long *ngFor/@for list and note the smoother updates.

🏠 Homework

  1. Take a list component, switch it to OnPush, update its array immutably, and add trackBy/track so only changed rows re-render.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →