---
title: Angular Performance Optimization - OnPush Change Detection
url: https://blog.octoperf.com/angular-performance-optimization---onpush-change-detection/
date: '2023-02-06'
authors:
- gpereira
categories:
- Development
tags:
- Angular
- Application Performance
- Debug
description: Understanding the Angular change detection cycle. Measuring its execution time. Understanding and using the `ChangeDetectionStrategy` to improve performances.
---

# Angular Performance Optimization - OnPush Change Detection

In our previous blog post we saw [why calling functions in Angular template expressions is bad practice and can have huge **performance impact on your application**](https://blog.octoperf.com/angular-performance-optimization---no-call-expression/).

Now it's time to take a look at [Angular ChangeDetectionStrategy](https://angular.io/api/core/ChangeDetectionStrategy) and see how it can help us improve the performances of our Angular applications.  


## Angular change detection cycle

Angular [change detection](https://angular.io/guide/change-detection) periodically checks if the application state changed to update the DOM (what's rendered in the web page) accordingly. 

Angular parses the components tree from the root to the leaves. 
In this blog post, you’ll learn **how to measure and optimize performances** of the change detection mechanism by skipping parts of your application and running change detection only when necessary.

## Use case: Books list

To check and improve performances of the detection cycle we will create a simple application that displays a list of *Books*.
**Performances start to dramatically decrease** around 100000 displayed books.

![Angular performance Books list application](https://blog.octoperf.com/img/blog/angular-performance-change-detection/books-list.png "Books list application")

A simple form allows the user to update the title of a book by specifying its index and a new title.

A *Book* is a simple mutable bean, defined as follows:

```typescript
export class Book {
  constructor(public id: number,
              public title: string,
              public author: string) {
  }
}
```

### `AppComponent`

The base application component (generated using [`ng new`](https://angular.io/tutorial/tour-of-heroes/toh-pt0)) is updated to contain a form and a simple list (`Array.from({length: 100000}).map()` generates a list with a large number of entries):

```typescript
import {Component, QueryList, TrackByFunction, ViewChildren} from '@angular/core';
import {Book} from "./book";
import {FormControl, FormGroup} from "@angular/forms";
import {BookChangeDetectorRefComponent} from "./book-change-detector-ref/book-change-detector-ref.component";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  indexControl = new FormControl<number>(0, {nonNullable: true});
  titleControl = new FormControl<string>('New title', {nonNullable: true});
  formGroup = new FormGroup({
    index: this.indexControl,
    title: this.titleControl
  })

  books: Book[] = Array.from({length: 100000}).map((value, index) => new Book(index, `Title ${index}`, `Author ${index}`));

  updateBook(): void {
    const value = this.formGroup.getRawValue();
    const book = this.books[value.index];
    if (book) {
      book.title = value.title;
    }
  }
}
```

Since the book is mutable, we can update the title by setting its value to the form value (`book.title = value.title`) in the `updateBook` method. 

The `app.component.html` HTML template simply displays the form and books list using the `*ngFor` directive:

```html
<form [formGroup]="formGroup">
  <input type="number" [formControl]="indexControl">
  <input type="text" [formControl]="titleControl">
  <button (click)="updateBook()">Update</button>
</form>

<ul>
  <li *ngFor="let book of books">
    <app-book [book]="book"></app-book>
  </li>
</ul>
```

### `BookComponent`

The books are displayed using a dedicated `BookComponent` that could hardly be made simpler:

```typescript
import {Component, Input} from '@angular/core';
import {Book} from "../book";

@Component({
  selector: 'app-book',
  templateUrl: './book.component.html',
  styleUrls: ['./book.component.scss']
})
export class BookComponent {

  @Input() book!: Book;

}
```

It's just a Book `@Input` displayed in the `book.component.html` HTML template:


```html
{{book.title}} {{book.author}}
```



When interacting with the application, we can feel that performances are suffering from the large number of books displayed:
**there is a perceptible input lag** when typing values in the form or clicking on the *Update* button.

But that's just a user feeling: we need a pragmatic way to measure the lag!

## Measure the change detection cycle time

Let's see how we can **measure the Angular change detection cycle execution time**!

There are two options:

* [Angular Debug Tools](#angular-debug-tools) to measure detection cycles duration in the browser console,
* [Angular Devtools](#angular-devtools) to profile performances using a Chrome extension.

### Angular Debug Tools

Angular debug tools allows to see how long the detection cycles take by typing a command in the Chrome console (press F12 to open the console):

![ng.profiler.timeChangeDetection()](https://blog.octoperf.com/img/blog/angular-performance-change-detection/time-change-detection.png "ng.profiler.timeChangeDetection() result")

Here we can see that the detection cycle takes around 60ms to execute.

But first we need to update the `main.ts` file of our Angular application to enable this debug option:

```typescript
import {ApplicationRef, enableProdMode} from '@angular/core';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';

import {AppModule} from './app/app.module';
import {environment} from './environments/environment';
import {enableDebugTools} from "@angular/platform-browser";

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .then((module) => {
    const appRef = module.injector.get(ApplicationRef);
    const appComponent = appRef.components[0];
    enableDebugTools(appComponent);
  })
  .catch(err => console.error(err));
```

After adding the call to `enableDebugTools()` in your application Bootstrap, you can type `ng.profiler.timeChangeDetection();` in the browser console to get an average duration of several detection cycles.

#### Record execution

Another available command that can be typed in the web browser console is `ng.profiler.timeChangeDetection({ record: true });`.

![ng.profiler.timeChangeDetection({ record: true })](https://blog.octoperf.com/img/blog/angular-performance-change-detection/time-change-detection-record.png "ng.profiler.timeChangeDetection({ record: true }) result")

The result can be seen by enabling the **Javascript Profiler** tab in your Chrome console:

1. Click on the dots icon in the upper right corner of the console,
2. Select the *More tools* menu,
3. Click on the *Javascript Profiler* menu item.
 
![Enable Javascript Profiler on Chrome](https://blog.octoperf.com/img/blog/angular-performance-change-detection/enable-javascript-profiler.png "Enable Javascript Profiler on Chrome")

Then you can open the Javascript Profile tab to see the record and **get more precise performance metrics**:

![Javascript Profiler Record](https://blog.octoperf.com/img/blog/angular-performance-change-detection/javascript-profiler-record.png "Javascript Profiler Record")

Here we can see that the update of our `AppComponent` template took 16.1ms and the `BookComponent` took 21.9ms.
That information is not very insightful for such a small application, but **a drill-down of performances can be interesting for larger uses cases**. 

Having to type a command in the console, this tool is limited to giving us performance metrics only when the application is idle (no user interaction).
That's still a very important information as the cycle should not take long when there are no user interactions or the lag will be perceptible.

### Angular Devtools

**Another solution is to install [Angular devtools](https://angular.io/guide/devtools) plugin for chrome.** 
It's a better option to check performances during interactions with the application as this plugin allows us to record performance metrics during a period of time.

First install the plugin for [Chrome](https://chrome.google.com/webstore/detail/angular-devtools/ienfalfjdbdpebioblfackkekamfmbnh) or [FireFox](https://addons.mozilla.org/en-GB/firefox/addon/angular-devtools/).

Then activate it by clicking on the *Extensions* menu (upper right corner of Chrome web browser):

![Angular performance Activate Devtools](https://blog.octoperf.com/img/blog/angular-performance-change-detection/activate-angular-devtools.png "Angular Devtools Extension")

Finally, open the console and select the *Angular* > *Profiler* tab (only available if the current browser tab displays an Angular application):

![Angular performance Devtools Profiler](https://blog.octoperf.com/img/blog/angular-performance-change-detection/angular-devtools-profiler.png "Angular Devtools Profiler")

Back to our Books list application, if we click on the *Record* button in the profiler, then submit a modification to a book title, we can see **how long it took to compute the update**:

![Angular Devtools Profiler Result](https://blog.octoperf.com/img/blog/angular-performance-change-detection/angular-profiler-record.png "Angular Devtools Profiler Result")

Among all information displayed by the profiler we can note:

* That the form submit took 369ms,
* That a **huge number of BookComponent** were impacted by this update! 

### `Default` Change detection strategy KPI

To sum up the **performance metrics of our Books list application using the default change detection strategy `ChangeDetectionStrategy.Default`**:

* The `ng.profiler.timeChangeDetection();` console command tells us that a detection cycle takes a bit more than 300ms to execute (idle application),
* The Angular DevTools shows that updating a book in the list take around 370ms.

How can we play with `ChangeDetectionStrategy` options to improve performances?  

## Using `OnPush` Change detection strategy

Setting the change detection strategy to *OnPush* is pretty simple.

### `BookComponent` update

We "just" need to configure it on the *BookComponent* `@Component` annotation:

```typescript
import {Component, Input} from '@angular/core';
import {Book} from "../book";

@Component({
  selector: 'app-book',
  templateUrl: './book.component.html',
  styleUrls: ['./book.component.scss'], 
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class BookComponent {

  @Input() book!: Book;

}
```

Making the application work properly with the *OnPush* strategy is another story!
If we interact now with the Books list application, we can this that a click on the *Update* button has no effect whatsoever.

Using `ChangeDetectionStrategy.OnPush` informs Angular that **our component only depends on its inputs to trigger the detection of changes to be made to the DOM**.
But since we only updated the title of the book, the [object reference](https://en.wikipedia.org/wiki/Pointer_(computer_programming)) did not change, preventing the detection by Angular!

### `AppComponent` update

We must change the `AppComponent.update()` method to create a new Book object instead of updating the existing one, thus updating the object reference in the `this.books` array:

```typescript
export class AppComponent {
    update(): void {
        const value = this.formGroup.getRawValue();
        const book = this.books[value.index];
        if (book) {
            book.title = value.title;
            const updated = new Book(book.id, value.title, book.author);
            this.books[value.index] = updated;
        }
    }
}
```

With this modification the change detection is properly triggered by the `update` method and the application behaves normally.

> **Note:**
> 
> Making your application work with `OnPush` Change detection strategy can be much harder than that, especially if your are not already [using immutable objects](#object-immutability-and-builders).  

### `OnPush` Change detection strategy performances

Now we can take a look at the impact in terms of performances.

First, the `timeChangeDetection()` debug method gives us 116ms per check (300ms previously):

![ng.profiler.timeChangeDetection() OnPush](https://blog.octoperf.com/img/blog/angular-performance-change-detection/time-change-detection-on-push.png "ng.profiler.timeChangeDetection() OnPush")

Then, the profiler gives us around 140ms (369ms previously) to handle the Book update:

![Angular Devtools Profiler OnPush](https://blog.octoperf.com/img/blog/angular-performance-change-detection/angular-profiler-record-on-push.png "Angular Devtools Profiler OnPush")

We can also note than only the AppComponent update takes time. There are not many BookComponent updates like with the previous (default) change detection strategy. 

**So we more than doubled the application performances by using the `ChangeDetectionStrategy.OnPush` option!**

### Object immutability and builders

We saw that updating a book would not work using the *OnPush* change detection strategy.

At OctoPerf we [develop a SaaS load testing solution](https://octoperf.com).
We think that [objects must be immutable](https://blog.octoperf.com/why-objects-must-be-immutable/) both for our Java backend and our Angular front.
**Object immutability is great both for code maintainability and application performances.**

The Book entity can be rewritten as an immutable object simply by adding the `readonly` keyword, preventing potential bugs caused by changes detection:  

```typescript
export class Book {
  constructor(public readonly id: number,
              public readonly title: string,
              public readonly author: string) {
  }
}
```

At OctoPerf we heavily relly on [Object Builders](https://blog.octoperf.com/the-builder-pattern/) and created an interface that is used across the whole application: 

```typescript
export interface Builder<T> {
  build(): T;
  from(t: T): Builder<T>;
}
```

But what if you are not convinced by the benefits of objects immutability? Is there another solution to use the *OnPush* change detection strategy?

## Using the `ChangeDetectorRef`

Yes there it is: you can **inject a `ChangeDetectorRef` instance and use it to tell Angular that some changes should be detected!** 

Let's rewrite our `BookComponent` and expose a public method `updateTitle` to update the current book title: 

```typescript
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
import {Book} from "../book";

@Component({
  selector: 'app-book',
  templateUrl: './book.component.html',
  styleUrls: ['./book.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class BookComponent {

  @Input() book!: Book;

  constructor(private changeDetectorRef: ChangeDetectorRef) {
  }

  public updateTitle(title: string): void {
    this.book.title = title;
    this.changeDetectorRef.detectChanges();
  }
}
```

The call to [`this.changeDetectorRef.detectChanges()`](https://angular.io/api/core/ChangeDetectorRef#detectChanges) tells Angular to check the current view and its children for potential changes.

Now, the responsibility to update the book title was taken from the `AppComponent` and given to `BookComponent`.
In the parent component, we simply inject the list of BookComponent using the [Angular `@ViewChildren` annotation](https://blog.octoperf.com/angular-viewchild-and-contentchildren-decorators/#using-viewchild-in-the-panel-component) and call the `updateTitle()` method of the proper BookComponent:

```typescript
import {Component, QueryList, ViewChildren} from '@angular/core';

export class AppComponent {
    @ViewChildren(BookComponent) bookComponents!: QueryList<BookComponent>;

    updateBookChangeDetectorRef(): void {
        const value = this.formGroup.getRawValue();
        this.bookComponents.get(value.index)?.updateTitle(value.title);
    }

}
```

That's another approach, and it's up to your development habits and coding standards to choose between the two options. 

### `changeDetectorRef.detectChanges()` performances

The application performances are similar to the previous option. The `timeChangeDetection()` debug method gives us 115ms per check:

![ng.profiler.timeChangeDetection() ChangeDetectorRef](https://blog.octoperf.com/img/blog/angular-performance-change-detection/time-change-detection-ref.png "ng.profiler.timeChangeDetection() ChangeDetectorRef")

Then, the profiler gives us around 140ms to handle the Book update:

![Angular Devtools Profiler ChangeDetectorRef](https://blog.octoperf.com/img/blog/angular-performance-change-detection/angular-profiler-record-ref.png "Angular Devtools Profiler ChangeDetectorRef")

## Conclusion

Even though **using the _OnPush_ change detection strategy allowed us to double to performances of our application**, there are still other paths to explore in order to have a proper user experience with such a large number of items displayed:

* [Using **Virtual Scroll** to only display a part of the Books list](https://blog.octoperf.com/angular-performance-optimization---virtual-scroll/),
* [Using **trackBy** to handle additions and removals](https://blog.octoperf.com/angular-performance-optimization---ngfor-trackby/).

> **Important:**
>
> Setting the _OnPush_ change detection strategy on a component enables this behavior for itself **but also for all its children!**
> Keep that in mind if you want to use it in you Angular projects.
