Крючок жизненного цикла ngDoCheck и KeyValueDiffers в Angular 5

Feb 24, 2018

В этой статье мы узнаем зачем нам нужен крючок жизненного цикла ngDoCheck и сервис KeyValueDiffers, воссоздав директиву ngStyle с нуля.

Сервис KeyValueDiffers

Сервис KeyValueDiffers является диффером, который отслеживает изменения внесенные в объект с течением времени, а также предоставляет API для реагирования на эти изменения.

Жизненный цикл ngDoCheck

Вы можете реализовать крючок жизненного цикла ngOnChanges, чтобы получать уведомления об изменениях, если ваше Input (входящее/@Input) свойство является примитивным типом или ваш Input меняет ссылку.

Однако, если ссылка на модель не меняется, но некоторое входящее свойство Input модели поменялось, то вы можете реализовать привязку жизненного цикла ngDoCheck, чтобы создать вашу собственную логику обнаружения изменений вручную.

Крюки жизненного цикла на сайте angular.io

Перейдем к кодированию.

import {Directive,
    DoCheck,
    ElementRef,
    Input,
    KeyValueChanges,
    KeyValueDiffer,
    KeyValueDiffers,
    Renderer2} from '@angular/core';


@Directive({selector: '[ngStyleTest]'})
export class NgStyleTest implements DoCheck {
    private _ngStyleTest: {[key: string]: string};
    private _differ: KeyValueDiffer<string, string|number>;

    constructor(
        private _differs: KeyValueDiffers,
        private _ngEl: ElementRef,
        private _renderer: Renderer2) {}

}

Мы создали ngStyleTest директиву и реализовали интерфейс DoCheck. Мы также внедрили (inject) сервисы KeyValueDiffers, Renderer2, и ссылку на хост элемент (ElementRef).

  @Input()
    set ngStyleTest(v: {[key: string]: string}) {
        this._ngStyleTest = v;
        if (!this._differ && v) {
            this._differ = this._differs.find(v).create();
        }
    }

Мы создаем входящий ngStyleTest как сеттер. Далее, если мы не имеем диффера*, то создаем новый диффер:
*Дифферы используются для обнаружения изменений в методе ngDoCheck.

this._differ = this._differs.find(v).create();

Метод find() ищет диффер соответствующий нашему значению.

Метод create() создает диффер и возвращает экземпляр DefaultKeyValueDiffer (null для ChangeDetectorRef)

  ngDoCheck() {
        if (this._differ) {
            const changes = this._differ.diff(this._ngStyleTest);
            if (changes) {
                this._applyChanges(changes);
            }
        }
    }

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

Например

<div [ngStyle]="style"></div>

// In our component constructor
this.style = { color: 'red' };

// After click event
this.style.color = 'blue'

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

private _applyChanges(changes: KeyValueChanges<string, string|number>): void {
    changes.forEachRemovedItem((record) => this._setStyle(record.key, null));
    changes.forEachAddedItem((record) => this._setStyle(record.key, record.currentValue));
    changes.forEachChangedItem((record) => this._setStyle(record.key, record.currentValue));
}

Данные методы особо не требуют пояснений, мы можем передать обратные вызовы каждому изменению, которые мы хотим знать. Каждые callback дает нам record типа KeyValueChangeRecord. Это объект с тремя полезными ключами key, currentValue и previousValue.

Теперь на каждое изменение мы вызываем метод _setStyle(), который вызывает setStyle() службы renderer, чтобы установить новый стиль на элементе.

private _setStyle(nameAndUnit: string, value: string|number|null|undefined): void {
    const [name, unit] = nameAndUnit.split('.');
    value = value != null && unit ? `${value}${unit}` : value;

    if (value != null) {
        this._renderer.setStyle(this._ngEl.nativeElement, name, value as string);
    } else {
        this._renderer.removeStyle(this._ngEl.nativeElement, name);
    }
}

Четсно говоря это исходники для ngStyle. Я просто попытался объяснить как это работает под капотом.

Использованы материалы Понимание ngDoCheck и KeyValueDiffers в Angular, а также актуальные исходники директивы ngStyle.

Для общего развития можно просмотреть статью - Angular ngDoCheck крючок жизненного цикла

Ну а чтобы понять все окончательно: angular2-change-detection

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