Skip to content

Angular OnPush Strategy

The OnPush change detection strategy tells Angular to skip checking a component unless specific conditions are met, a common and effective performance optimization. This lesson covers how to use it correctly.

What Does OnPush Change?

By default, Angular checks every component on every change detection cycle. Setting a component's changeDetection to ChangeDetectionStrategy.OnPush tells Angular to skip checking that component unless one of a small set of specific triggers occurs.

This can meaningfully reduce the amount of work Angular does per cycle in large component trees, especially for components that render mostly static or rarely-changing data.

@Component({
  selector: 'app-user-card',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: '<p>{{ user.name }}</p>',
})
export class UserCardComponent {
  @Input() user!: User;
}

With OnPush, this component only re-checks when user is reassigned to a new object reference, not when a property on the same object mutates in place.

Setting the Strategy

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  // ...
})
export class MyComponent {}
  • changeDetection: ChangeDetectionStrategy.OnPush is set directly in a component's decorator metadata.
  • Once set, Angular checks the component only when its inputs change by reference, an event originates from within it, or it's manually marked for check.
  • Signals read directly in the template also trigger a check for OnPush components automatically.
  • Components using signals for all their reactive state effectively behave like OnPush was designed for.

OnPush Triggers Cheatsheet

The specific conditions that cause an OnPush component to be checked.

Trigger Example
Input reference changes Parent passes a new object/array reference via [input]
Event originates within the component A (click) handler runs inside the component itself
A signal read in the template updates count() used in the template, count.set() called
markForCheck() called manually Explicitly telling Angular to check on the next cycle
Async pipe emits a new value {{ data$ | async }} receiving a new emission

OnPush Requires Immutable Data Patterns

Because OnPush only reacts to input *reference* changes, mutating an object or array in place (this.user.name = 'New Name') will not trigger a re-check, the object reference itself never changed. Always create a new object/array reference when updating data that flows into OnPush components.

// WRONG: mutates in place, OnPush component won't notice
this.user.name = 'New Name';

// RIGHT: creates a new reference
this.user = { ...this.user, name: 'New Name' };

OnPush Works Naturally With the Async Pipe

The async pipe internally calls markForCheck() whenever the underlying Observable emits a new value, which means OnPush components using the async pipe update correctly without any extra work.

@Component({
  selector: 'app-user-list',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [AsyncPipe],
  template: '@for (u of (users$ | async); track u.id) { <p>{{ u.name }}</p> }',
})
export class UserListComponent {
  users$ = inject(UserService).getUsers();
}

OnPush and Signals Work Especially Well Together

Signals were designed with OnPush-style checking in mind: reading a signal in a template automatically registers the right kind of dependency, so updating that signal correctly triggers a check for an OnPush component with zero extra configuration.

@Component({
  selector: 'app-counter',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: '<button (click)="count.set(count() + 1)">{{ count() }}</button>',
})
export class CounterComponent {
  count = signal(0);
}

Common Mistakes

  • Mutating objects/arrays in place and expecting an OnPush component to notice the change.
  • Forgetting markForCheck() when manually updating state from outside Angular's normal event flow (like a raw third-party library callback).
  • Applying OnPush broadly across a codebase without adopting the immutable data habits it requires, causing subtly stale UI.
  • Assuming OnPush skips rendering the component entirely, it skips *checking*, the component still renders normally when it is checked.

Key Takeaways

  • OnPush tells Angular to skip checking a component unless specific triggers occur.
  • It requires treating inputs immutably, always create new references instead of mutating in place.
  • The async pipe and signals both integrate naturally with OnPush, calling markForCheck() (or an equivalent) automatically.
  • OnPush is a targeted performance optimization, not a universal default to apply blindly everywhere.

Pro Tip

Adopt OnPush alongside signals and the async pipe as your default reactive pattern, together they give you OnPush's performance benefits automatically, without needing to remember to call markForCheck() manually in most cases.