How to use OnPush change detection in Angular

OnPush change detection in Angular dramatically improves performance by skipping change detection when input references don’t change. As the creator of CoreUI with over 12 years of Angular experience since 2014, I’ve used OnPush to optimize performance in enterprise dashboards. OnPush strategy runs change detection only when input properties change reference, events fire, or Observables emit new values. This approach reduces unnecessary checks and makes applications faster, especially with large component trees.

Use ChangeDetectionStrategy.OnPush to optimize Angular performance by limiting when change detection runs.

Basic OnPush configuration:

// user-list.component.ts
import { Component, Input, ChangeDetectionStrategy } from '@angular/core'

@Component({
  selector: 'app-user-list',
  template: `
    <div>
      <h2>Users</h2>
      <div *ngFor="let user of users">
        {{ user.name }} - {{ user.email }}
      </div>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserListComponent {
  @Input() users: User[] = []
}

Immutable data updates:

// parent.component.ts
import { Component } from '@angular/core'

@Component({
  selector: 'app-parent',
  template: `
    <button (click)="addUser()">Add User</button>
    <app-user-list [users]="users"></app-user-list>
  `
})
export class ParentComponent {
  users = [
    { id: 1, name: 'John', email: '[email protected]' },
    { id: 2, name: 'Jane', email: '[email protected]' }
  ]

  // WRONG - mutation doesn't trigger OnPush
  addUserWrong() {
    this.users.push({ id: 3, name: 'Bob', email: '[email protected]' })
  }

  // CORRECT - new reference triggers OnPush
  addUser() {
    this.users = [...this.users, { id: 3, name: 'Bob', email: '[email protected]' }]
  }

  // CORRECT - updating single user
  updateUser(id: number, name: string) {
    this.users = this.users.map(user =>
      user.id === id ? { ...user, name } : user
    )
  }

  // CORRECT - removing user
  removeUser(id: number) {
    this.users = this.users.filter(user => user.id !== id)
  }
}

OnPush with Observables:

// data.component.ts
import { Component, ChangeDetectionStrategy } from '@angular/core'
import { Observable } from 'rxjs'
import { DataService } from './data.service'

@Component({
  selector: 'app-data',
  template: `
    <div *ngIf="data$ | async as data">
      <h2>{{ data.title }}</h2>
      <p>{{ data.content }}</p>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class DataComponent {
  data$: Observable<any>

  constructor(private dataService: DataService) {
    this.data$ = this.dataService.getData()
  }
}

Manual change detection with OnPush:

// counter.component.ts
import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'

@Component({
  selector: 'app-counter',
  template: `
    <div>
      <p>Count: {{ count }}</p>
      <button (click)="increment()">Increment</button>
      <button (click)="startTimer()">Start Timer</button>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
  count = 0

  constructor(private cdr: ChangeDetectorRef) {}

  // Event handlers automatically trigger change detection
  increment() {
    this.count++
  }

  // External updates need manual trigger
  startTimer() {
    setInterval(() => {
      this.count++
      // Manually mark for check
      this.cdr.markForCheck()
    }, 1000)
  }
}

OnPush with EventEmitter:

// child.component.ts
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'

@Component({
  selector: 'app-child',
  template: `
    <div>
      <p>{{ message }}</p>
      <button (click)="notify()">Notify</button>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {
  @Input() message: string
  @Output() messageChanged = new EventEmitter<string>()

  notify() {
    // Events trigger change detection in parent
    this.messageChanged.emit('Message from child')
  }
}

OnPush with complex objects:

// user-profile.component.ts
import { Component, Input, ChangeDetectionStrategy } from '@angular/core'

interface User {
  id: number
  name: string
  profile: {
    age: number
    city: string
  }
}

@Component({
  selector: 'app-user-profile',
  template: `
    <div>
      <h3>{{ user.name }}</h3>
      <p>Age: {{ user.profile.age }}</p>
      <p>City: {{ user.profile.city }}</p>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserProfileComponent {
  @Input() user: User
}

// Parent component - correct updates
@Component({
  template: `<app-user-profile [user]="user"></app-user-profile>`
})
export class ParentComponent {
  user: User = {
    id: 1,
    name: 'John',
    profile: { age: 30, city: 'New York' }
  }

  // WRONG - nested mutation
  updateCityWrong() {
    this.user.profile.city = 'Boston'
  }

  // CORRECT - new object reference
  updateCity() {
    this.user = {
      ...this.user,
      profile: {
        ...this.user.profile,
        city: 'Boston'
      }
    }
  }
}

OnPush with services:

// notification.service.ts
import { Injectable } from '@angular/core'
import { BehaviorSubject } from 'rxjs'

@Injectable({
  providedIn: 'root'
})
export class NotificationService {
  private notificationSubject = new BehaviorSubject<string>('')
  notification$ = this.notificationSubject.asObservable()

  showNotification(message: string) {
    this.notificationSubject.next(message)
  }
}

// notification.component.ts
import { Component, ChangeDetectionStrategy } from '@angular/core'
import { NotificationService } from './notification.service'

@Component({
  selector: 'app-notification',
  template: `
    <div *ngIf="notification$ | async as message" class="notification">
      {{ message }}
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class NotificationComponent {
  notification$ = this.notificationService.notification$

  constructor(private notificationService: NotificationService) {}
}

OnPush with trackBy:

// list.component.ts
import { Component, Input, ChangeDetectionStrategy } from '@angular/core'

@Component({
  selector: 'app-list',
  template: `
    <ul>
      <li *ngFor="let item of items; trackBy: trackById">
        {{ item.name }}
      </li>
    </ul>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ListComponent {
  @Input() items: any[]

  trackById(index: number, item: any) {
    return item.id
  }
}

Detach and reattach:

// performance.component.ts
import { Component, ChangeDetectorRef, ChangeDetectionStrategy, OnDestroy } from '@angular/core'

@Component({
  selector: 'app-performance',
  template: '<div>{{ data }}</div>',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PerformanceComponent implements OnDestroy {
  data: string

  constructor(private cdr: ChangeDetectorRef) {}

  startHeavyOperation() {
    // Detach from change detection during heavy operation
    this.cdr.detach()

    // Perform operation
    this.processData()

    // Reattach and update
    this.cdr.reattach()
    this.cdr.markForCheck()
  }

  processData() {
    // Heavy computation
    this.data = 'Processed'
  }

  ngOnDestroy() {
    // Clean up
    this.cdr.detach()
  }
}

Best Practice Note

Use OnPush for leaf components that display data passed via inputs. Always create new object references when updating—use spread operator or Array methods like map, filter. Async pipe automatically triggers change detection—perfect for OnPush components. Call markForCheck() when updating from external sources like timers or WebSocket callbacks. Combine OnPush with trackBy for optimal ngFor performance. Use immutable update patterns consistently across your application. This is how we optimize CoreUI Angular applications—OnPush change detection throughout component trees, immutable data updates, and strategic use of ChangeDetectorRef for maximum performance.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
How to Detect a Click Outside of a React Component
How to Detect a Click Outside of a React Component

How to Remove Elements from a JavaScript Array
How to Remove Elements from a JavaScript Array

How to concatenate a strings in JavaScript?
How to concatenate a strings in JavaScript?

How to Center a Button in CSS
How to Center a Button in CSS

Answers by CoreUI Core Team