Angular. DoCheck и KeyValueDiffers

Если @Input является примитивом или объектом, у которого меняется ссылка, то изменения обнаруживаются в хуке ngOnChanges. Если объект мутирует, то такое изменение можно отследить через хук ngDoCheck. Вызывается чрезмерное количество раз, поэтому следует избегать его использования, чтобы не было проблем с производительностью.

Допустим имеется @Input() объект с одним свойством:

interface Data { name: string };

Отследить его изменение можно следующим образом:

  • Сохранить объект при изменении ссылки в ngOnChanges
  • Проверять name в ngDoCheck
export class DataComponent {
  @Input() data: Data;
  private localData: Data;

  constructor(private cdr: ChangeDetectorRef) {}

  ngOnChanges() {
    this.localData = this.data;
  }

  ngDoCheck() {
    if (this.localData.name !== this.data.name) {
      this.cdr.markForCheck();
    }
  }
}

Если интерфейс объекта более сложный или количество свойств неизвестно, то надо проходить по всем свойствам и их сравнивать. Это можно сделать с помощью функций сравнения объектов, например equals или isEqual. Однако в Angular есть встроенный механизм, который называется Differs.

  • IterableDiffer - сравнение итерируемых объектов, например массивов
  • KeyValueDiffers - сравнение свойств объектов

Первоначально требуется создать начальное значение, например пустой объект {}

constructor(private differs: KeyValueDiffers) {
    this.differ = this.differs.find({}).create();
}

Далее производить сравнение

const changes = this.differ.diff({ name: 'Вася' });
if (changes) {
    // объект изменился
}

Пример использования

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

@Component({
  selector: "app-hello",
  templateUrl: "./hello.component.html",
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class HelloComponent implements DoCheck {
  @Input() data: { name: string };
  private differ = this.differs.find({}).create();

  constructor(
    private cdr: ChangeDetectorRef,
    private differs: KeyValueDiffers
  ) {}

  ngDoCheck() {
    const changes = this.differ.diff(this.data);
    if (changes) {
      console.log("Object changed");
      changes.forEachChangedItem(item => console.log("Changed", item));
      changes.forEachAddedItem(item => console.log("Added", item));
      changes.forEachRemovedItem(item => console.log("Removed", item));
      this.cdr.markForCheck();
    } else {
      console.log("No changes");
    }
  }
}

StackBlitz

Похожие записи

Angular Let Directive

*ngIf не отображает содержимое в falsy случаях (0, null, undefined) на async pipe, в пакете @rx-angular/template предлагается решение