Using OnPush Change Detection and Updating the UI
Tags: angular
Published: 2019-05-29 22:45:27

---
title: Using OnPush Change Detection and Updating the UI
published: true
tags: [angular]
canonical_url: https://www.prestonlamb.com/blog/angular-on-push-ui-updates
---

Angular has two types of change detection. As a brief review, those two types are the `Default` strategy and the `OnPush` strategy.

The `Default` strategy runs every time any change happens in the app. It could be a button click, an HTTP call, a `setTimeout`, or any other type of timer or user interaction.

The `OnPush` strategy on the other hand, only runs if one of four conditions are met:

1. The `Input` reference changes
2. An event originated from the component or one of its children (an `@Output` event fires)
3. Change detection is explicitly run
4. The `async` pipe receives new data

In this article, we'll talk mainly about how to make sure the UI updates properly when case 1 or 2 occurs. If you'd like more details on Angular's change detection, go read [this article](https://netbasal.com/a-comprehensive-guide-to-angular-onpush-change-detection-strategy-5bac493074a4) by [Netanel Basal](https://netbasal.com/@NetanelBasal).

Now, I came across this topic while watching [Dan Wahlin](https://www.twitter.com/DanWahlin)'s latest Pluralsight course on Angular Architecture. He talked about the benefits of using the `OnPush` change detection strategy, but that when doing so there may be times where your UI doesn't update. Here's a situation where your application may not update automatically, even though you are using the `@Input` and `@Output` methods in the list above that should trigger change detection.

Let's pretend you have an application where a user can add or edit their favorite books. Each book has a title, an author, a rating, and an ID. When the application loads, it calls a service to get the list of books, and then caches them in the service. After that, when the books are retrieved they are just retrieved from the service. The user can select a book to edit or can add a new book. When that happens, the book is added directly to the cached list of books, or updated in place. As for the components, you have a parent component which contains a child component that lists the books and another child component that allows for the editing of those books. Those two child components are the ones with the `OnPush` change detection set. Below you can see the interface and the service as an example.

```ts
export interface Book {
  id: number;
  author: string;
  title: string;
  rating: number;
}

@Injectable({ providedIn: 'root' })
export class BooksService {
  private books: Book[] = [{
    id: 1,
    author: 'Brandon Mull',
    title: 'Fablehaven',
    rating: 3
  }];

  constructor() { }

  getBooks(): Observable {
    return of(this.books);
  }

  updateBook(book: Book): Observable {
    const idx = this.books.findIndex((bk: Book) => bk.id === book.id);
    this.books[idx] = book;
    return of(this.books);
  }

  addBook(): Observable {
    console.log('called')
    this.books.push({
      id: this.books.length + 1,
      title: 'New Book',
      author: 'Some Author',
      rating: 3
    });

    return of(this.books);
  }
}
```

Now, in this situation, when a book is added or edited and the change made to the cached list of books, the UI for the application won't update. This is because the inputs to the two children components are either objects (for the selected book to edit) or an array (to display all the books). Because they are objects/arrays, any change made to the contents will not trigger a change detection cycle. That's because they are still pointing to the same location in memory; in other words the inputs are passed by reference. Technically, according to Angular, the variable never changed so change detection doesn't run.

We can change this by the way we return the list of objects each time one is added or edited. Instead of returning the same array each time, we can return a cloned array. This changes its location in memory each time the list is updated and the UI is updated as a result. Here's an example of the service written that way:

```ts
@Injectable({ providedIn: 'root' })
export class Books2Service {
  private books: Book[] = [{
    id: 1,
    author: 'Brandon Mull',
    title: 'Fablehaven',
    rating: 3
  }];

  constructor() { }

  private cloneBooksArray() {
    return JSON.parse(JSON.stringify(this.books));
  }

  getBooks(): Observable {
    return of(this.cloneBooksArray());
  }

  updateBook(book: Book): Observable {
    const idx = this.books.findIndex((bk: Book) => bk.id === book.id);
    this.books[idx] = book;
    return of(this.cloneBooksArray());
  }

  addBook(): Observable {
    this.books.push({
      id: this.books.length + 1,
      title: 'New Book',
      author: 'Some Author',
      rating: 3
    });

    return of(this.cloneBooksArray());
  }

}
```

Now, I know this is a little tough to understand in writing only, so I prepared a [StackBlitz example](https://stackblitz.com/edit/pjlamb12-change-detection-example) to demonstrate. All you need to do is to change the service that is used in the `app.component.ts` file. The `BooksService` doesn't clone the books array that is returned; the `Books2Service` does clone the array. Thus, the second service updates the UI while the first service doesn't.

In this example, since it's a pretty basic one, I used `JSON.stringify()` and `JSON.parse()` to clone the array each time. If the objects or arrays are more complex, or have date properties, you may want to reach for a better solution. There are many third-party libraries out there that will clone objects for you, and any one of them would work.