Знакомство с Differs Angular

Jul 21, 2019

У Angular differs, вероятно, наименеее известные API; Это высокооптимизированные строительные блоки используемые во всей среде Angular ( ngClass, ngStyle, ngFor, etc.)

Вы, конечно, не будете использовать их ежедневно, однако, при правильных остоятельствах, differs могут быть очень полезными. Если вы когда-нибудь спросите себя:

Мне недостаточно знать, что что-то изменилось, я хочу знать что изменилось.

Тогда вы должны знать, что differs Angular и есть ответ.

Angular предлагает два типа differs: IterableDiffer и KeyValueDiffers.

IterableDiffer API

IterableDiffer - это API, которое отслеживает изменения на iterable 'итерируемых' объектах, а также предоставляет API позволяющее вам реагировать на эти изменения.

Давайте взглянем как мы можем использовать его, создав собственную директиву ngClass.

@Directive({
    selector: '[myNgClass]'
})
export class MyNgClassDirective {
    @Input() myNgClass = [];
    private iterableDiffer: IterableDiffer<string> | null;

    constructor(private iterable: IterableDiffers) {
        this.iterableDiffer = this.iterable.find(this.myNgClass).create();
    }

}

Метод find проверяет - есть ли в Angular differ, который поддерживает тип данного параметра. Мы можем изучить это процесс в исходном коде:

class IterableDiffers {

  find(iterable: any): IterableDifferFactory {
    const factory = this.factories.find(f => f.supports(iterable));
    if (factory != null) {
      return factory;
    }
  }

}
class DefaultIterableDifferFactory {
  supports(obj: Object|null|undefined): boolean {
     return isListLikeIterable(obj);
  }

}

Будучи найденным, метод create() возвращает экземпляр этого differ, в нашем случае DefaultKeyValueDiffer.

class DefaultIterableDifferFactory {

  create<V>(trackByFn?: TrackByFunction<V>): DefaultIterableDiffer<V> {
    return new DefaultIterableDiffer<V>(trackByFn);
  }

}

Отлично, мы имеем differ. Сейчас давайте взглянем как мы можем использовать его. Наша цель - слушать любые изменения во входящем свойстве ngMyClass и на основе этого добавлять или удалять класс.

@Directive({
    selector: '[myNgClass]'
})
export class NbClassDirective {
  ...
  ngOnInit() {
    this.myNgClass.forEach(className => {
      this.renderer.addClass(this.host.nativeElement, className);
    });
  }

  ngDoCheck() {
    const classChanges = this.iterableDiffer.diff(this.myNgClass);

    if (classChanges) {

      classChanges.forEachAddedItem((record: IterableChangeRecord<any>) => {
        this.renderer.addClass(this.host.nativeElement, record.item);
      });

      classChanges.forEachRemovedItem((record: IterableChangeRecord<any>) => {
        this.renderer.removeClass(this.host.nativeElement, record.item);
      });
    }
  }

}

Мы можем проверить изменения, вызвав метод diff() с новым значением.

Если нет никаких изменений, то возвращаемое значение будет null, иначе возвращаемое значение будет объектом, который предоставляет методы (названия которых говорят сами за себя), при помощи которых мы можем реагировать на эти изменения.

В нашем случае мы хотим знать был ли элемент удален или добавлен и на основе этого принять нужные действия.

Демо на stackblitz

KeyValueDiffers API

KeyValueDiffers API работает схожим образом, за исключением того, что он отслеживает изменения в объекте, а не в итерируемом объекте.

Те же самые концепции, упомянутые в первом разделе, также применимы и здесь. Давайте изменим директиву, которую мы создали и получим объект вместо массива.

@Directive({
    selector: '[myNgClass]'
})
export class MyNgClassDirective {
  @Input() myNgClass = {};
  private differ: KeyValueDiffer<any, any>;

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

  ngOnInit() {
    ...add the initial classes
  }
}

При использовании KeyValueDiffers, реализация методов find() и create() следующая:

export class DefaultKeyValueDifferFactory<K, V> implements KeyValueDifferFactory {

    supports(obj: any): boolean {
      return obj instanceof Map || isJsObject(obj);
    }

    create<K, V>(): KeyValueDiffer<K, V> {
      return new DefaultKeyValueDiffer<K, V>();
    }
  }

Теперь, когда у нас есть differ, воспользуемся им:

ngDoCheck() {

    const changes = this.differ.diff(this.myNgClass);

    if (changes) {
        changes.forEachAddedItem((record: KeyValueChangeRecord<any, any>) => {
            if (record.currentValue === true) {
                this.renderer.addClass(this.host.nativeElement, record.key);
            }
        });

        changes.forEachChangedItem((record: KeyValueChangeRecord<any, any>) => {
            if (record.currentValue === true) {
                this.renderer.addClass(this.host.nativeElement, record.key);
            } else {
                this.renderer.removeClass(this.host.nativeElement, record.key);
            }
        });

        changes.forEachRemovedItem((record: KeyValueChangeRecord<any, any>) => {
            this.renderer.removeClass(this.host.nativeElement, record.key);
        });
    }

}

Аналогично. Когда мы не получаем null у нас есть понятные методы, которые дают нам все что нам нужно.

Демо на stackblitz

Добавить комментарий