Директивы в Angular

Jun 18, 2018

Директива и ElementRef

@Directive({
    selector: '[appCustomTest]'
})
export class CustomTestDirective implements OnInit {
    @HostBinding('style.border') border: string;
    constructor(private el: ElementRef) {
        // this.el.nativeElement - является обычным Dom узлом
    }

ElementRef - предоставляется Angular при создании нового экземпляра директивы и ссылается на управляющий элемент.

Класс ElementRef предоставляет свойство nativeElement для доступа к элементу в модели DOM и всем соответствующим свойствам (classlist etc.).

Методы жизненного цикла директивы

Значение входного свойство директивы задается лишь после создания объекта директивы, то есть мы не сможем получить доступ к входному свойству в конструкторе. Чтобы решить эту проблему в Angular реализованы крючки (методы) жизненного цикла директивы.

  • ngOnInit - вызывается после инициализации всех входных свойств
  • ngOnChanges (OnChanges()) - вызывается при изменении значений входных свойств
  • ngDoCheck - вызывается при запуске Angular процесса обнаружения изменений
  • ngAfterContentInit - вызывается после инициализации контента директивы
  • ngAfterContentChecked - вызывается после анализа контента директивы при запуске Angular процесса обнаружения изменений
  • ngOnDestroy - вызывается перед уничтожением директивы
ngOnChanges(changes: SimpleChanges) {
    for (let propName in changes) {
        let chng = changes[propName];
        let cur  = JSON.stringify(chng.currentValue);
        let prev = JSON.stringify(chng.previousValue);
        this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
    }
}

Создание привязок для управляющих элементов (работаем с DOM API: события, свойства)

Вместо использования DOM API Angular предоставляет позволяет привязывать свойства и методы посредством привязок свойств и событий.

Привязка управляющего элемента осуществляется при помощи декораторов @HostBinding и @HostListener, которые определяются в модуле @angular/core.

@HostBinding('style.border') border: string;

@HostListener('mouseover') onMouseOver() {
    this.border = '5px solid green';
}

Двусторонняя привязка для директивы и управляющего элемента

Директивы поддерживают двустороннюю пользовательскую привязку [()]. Функциональность двусторонней привязки опирается на соглашение об именах.

<div class="form-group">
    <label>Имя:</label>
    <input class="form-control"
        [(myModel)]="newProduct.name"
        #myModel="myModel" />
    <div>Направление: {{myModel.direction}}</div>
</div>

<form [formGroup]="form" >
    <label>Имя</label>
    <input class="form-control"
           name="name"
           [(ngModel)]="newProduct['name']"
           formControlName="name" />
</form>
@Directive({
    selector: "input[myModel]",
    exportAs: "myModel"
})
export class myModel {

    direction: string = "None";

    @Input("myModel")
    modelProperty: string;

    @HostBinding("value")
    fieldValue: string = "";

    // от модели директиве
    ngOnChanges(changes: { [property: string]: SimpleChange }) {
        let change = changes["modelProperty"];
        if (change.currentValue != this.fieldValue) {
            this.fieldValue = changes["modelProperty"].currentValue || "";
            this.direction = "Model";
        }
    }

    @Output("myModelChange")
    update = new EventEmitter<string>();


    // от директивы модели
    @HostListener("input", ["$event.target.value"])
    updateValue(newValue: string) {
        this.fieldValue = newValue;
        this.update.emit(newValue);
        this.direction = "Element";
    }
}

exportAs для директивы

exportAs задает имя, которое будет использоваться для для ссылок на директиву в переменых шаблона. смотрите пример выше и в частности на #myModel="myModel". #myModel="myModel": переменной шаблона #myModel задается значение (имя), которое мы прописали в exportAs. Имена не обязательно должны быть одинаковыми - просто так нагляднее.

Кастомная структурная директива

Следующий код идентичен:

    <div *ngIf="hero" class="name">{{hero.name}}</div>

      идентичен:

    <ng-template [ngIf]="hero">
        <div class="name">{{hero.name}}</div>
    </ng-template>

Реализуем структурную директиву customIf, которая будет работать аналогично директиве *ngIf. * указывает на то, что это структурная директива, которая использует компактный синтаксис.

Структурная директива, в отличие от обычной, может удалять элемент из DOM или наоборот добавлять его в DOM. Структурная директива автоматом выбрасывает наш элемент из DOM (при инициализации).

<label>
    <input type="checkbox" [(ngModel)]="showDiv" /> show Div
</label>
<div *customIf="showDiv">
    тестируем кастомную директиву
</div>
@Directive({
    selector: "[customIf]"
})
export class CustomIfDirective {
    constructor(private container: ViewContainerRef,
                private template: TemplateRef<Object>) { }

    @Input("customIf")
    showDiv: boolean;

    ngOnChanges(changes: { [property: string]: SimpleChange }) {
        let change = changes["showDiv"];
        if (!change.isFirstChange() && !change.currentValue) {
            this.container.clear();
        } else if (change.currentValue) {
            // Добавляем шаблон в контейнер представлений:
            this.container.createEmbeddedView(this.template);
        }
    }

    /*
    // показываем директиву (добавляем в DOM):
    this.container.createEmbeddedView(this.template);

    */

}

Первый признак того, что директива является структурной, можно увидеть в конструкторе, где переданные параметры являются типами ViewContainerRef и TemplateRef.

ViewContainerRef - объект, используемый для управлением контейнером представлений (представлений/элементами HTML с привязками, директивами, выражениями). Иначе это место в HTML, в котором лежит template, то есть это обертка, которая создается вокруг нашей директивы.

TemplateRef - объект, который представляет содержимое элемента template или это и есть элемент и его содержимое, к которому применяется директива.

Свойства и методы ViewContainerRef:

  • element - элемент ElementRef, определяющий контейнерный элемент.
  • createEmbeddedView(template) - метод использует шаблон для создания нового представления. Может получать необязательный аргумент с данными контекста. В результате создается объект ViewRef.
  • clear() - удаляет все представления из контейнера
  • length()
  • indexOf(view)
  • insert(view,index)
  • remove(index)
  • detach(index)
  • etc. см. документацию

Отметьте: Директива может использовать метод ngOnChanges, чтобы реагировать на изменения в модели данных. Компактный синтаксис структурной директивы позволяет избавиться от элемента template: директива применяется к самому внешнему элементу. Анализирую шаблон, Angular сделает так будто template есть.

Как под капотом работает структурная директива в Angular?

Увидев директиву вида *appTestDi, Angular переписывает нижеприведенный код,

<app-item *appTestDi="va" ></app-item>

на следующий (где уже на ng-template навешивается наша директива в стандартном виде [appTestDi]):

<ng-template< [appTestDi]="va"
    <app-item  ></app-item>
</ng-template<

Два вышеприведенных кода идентичны. А из второго примера можно понять чем являются ViewContainerRef и TemplateRef.

  • ng-template - это и есть ViewContainerRef
  • <app-item ></app-item> - это TemplateRef
Добавить комментарий