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.
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.
// 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.
<!-- old syntax -->
<li *ngFor="let user of users; trackBy: trackById">{{ user.name }}</li>// 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).
| Technique | What it saves |
|---|---|
OnPush | Skips re-checking components that did not change |
trackBy / track | Reuses list rows instead of rebuilding all |
async pipe | Auto subscribe/unsubscribe; plays well with OnPush |
| Immutable updates | Lets 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?
✍️ Practice
- Add
OnPushto a list-item component and update its data by replacing the array. - Add
trackBy(ortrack) to a long*ngFor/@forlist and note the smoother updates.
🏠 Homework
- Take a list component, switch it to OnPush, update its array immutably, and add trackBy/track so only changed rows re-render.