Bättre vinkelprestanda med ChangeDetectionStrategy OnPush

När du startar en ny Angular-applikation är standardändringsdetekteringen som används i varje komponent ChangeDetectionStrategy.Default . Detta innebär att Angular som standard kontrollerar varje del av applikationen för ändringar när till exempel användarhändelser aktiveras, API-förfrågningar görs eller timers är aktiva.

För att visa detta i aktion har jag hånat ett litet ToDo-exempel (det måste vara todos igen eller hur?):

import { Component } from "@angular/core";

@Component({
  selector: "app-todo-list",
  template: `
    <h1>Some todos 📝</h1>
    <button (click)="add()">Add Todo</button>
    <div class="todos">
      <app-todo [todo]="todo" *ngFor="let todo of todos"></app-todo>
    </div>
  `,
  styles: [
    `
      .todos {
        margin-top: 1.4rem;
        display: flex;
        flex-direction: column;
      }

      button {
        border: solid #193549 2px;
      }
    `
  ]
})
export class TodoListComponent {
  public todos = [{ title: "Number 1" }, { title: "Number 2" }];

  public add() {
    this.todos = [...this.todos, { title: `Number ${this.todos.length + 1}` }];
  }
}

TodoListComponent.ts

import { Component, Input } from "@angular/core";

@Component({
  selector: "app-todo",
  template: `
    <div class="todo">{{ todo.title }} -> {{ didChangeDetectionRun }}</div>
  `,
  styles: [
    `
      .todo {
        margin-bottom: 0.5rem;
      }
    `
  ]
})
export class TodoComponent {
  @Input() todo;

  get didChangeDetectionRun() {
    const date = new Date();

    return `Change detection was run at
      ${date.getHours() < 10 ? "0" : ""}${date.getHours()}:
      ${date.getMinutes() < 10 ? "0" : ""}${date.getMinutes()}:
      ${date.getSeconds() < 10 ? "0" : ""}${date.getSeconds()}
    `;
  }
}

TodoComponent.ts

När en Todo läggs till via Add Todo-knappen kontrollerar Angular hela trädet för ändringar, t.ex. varje TodoComponent kontrollerar om ändringar av den godkända uppgiften hände. Tiden då ändringsdetekteringen kördes visas bredvid en uppgift, och du kan se att den är densamma för varje uppgift.



Åh nej, förändringsdetektering kördes för alla våra uppgifter :(

Nu, eftersom bara en uppgift lades till, skulle vi vilja lämna de tidigare uppgifterna orörda, eller hur?
Vi kan göra detta genom att ställa in changeDetectionStrategy för TodoComponent till OnPush.

import { Component, Input, ChangeDetectionStrategy } from "@angular/core";

@Component({
  selector: "app-todo",
  template: `
    <div class="todo">{{ todo.title }} -> {{ didChangeDetectionRun }}</div>
  `,
  styles: [
    `
      .todo {
        margin-bottom: 0.5rem;
      }
    `
  ]
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodoComponent {
  // Some unchanged code.
}

Uppdaterad TodoComponent.ts

När nu en uppgift läggs till i vår lista, går inte de tidigare renderade att göra-komponenterna igenom förändringsdetektering, eftersom deras Indatareferens har inte ändrats.


Ändringsdetektering ingår!

I det här lilla exemplet skulle detta inte göra någon stor skillnad i prestanda. Men i en stor applikation med många uttryck kan förändringsdetekteringen vara onödigt långsam om allt kontrolleras hela tiden.

Med OnPush , Angular söker endast efter ändringar i följande fall:

  1. Inmatningsreferensen för en komponent ändras, som i vårt tidigare exempel
  2. En händelse kommer från komponenten eller från ett av dess barn. Detta är till exempel fallet när du har en bindning i en mall som <button (click)="onSave($event)">Save</button>
  3. När förändringsdetektering körs explicit, t.ex. du sätter en detectChanges() i din komponent.
  4. När det asynkrona röret används i en mall och ett nytt värde passerar genom en observerbar, görs förändringsdetekteringen implicit.

I den applikation jag för närvarande arbetar med har vi nu börjat implementera dessa förändringar som ett sätt att förbättra den övergripande prestandan, särskilt i komplexa vyer med ett stort antal databindningar.

Jag hoppas att detta hjälper till att förstå beteendet för förändringsdetektering av Angular lite bättre. Jag har mest bara skrivit ner det här för att komma ihåg det själv efter att ha undersökt hur det mesta fungerar, men det kanske hjälper några av er :)

Tack för att du läser och ha kul med kodningen.