Перейти к содержанию

Создание директив

Довольно часто при разработке Angular приложения приходится создавать пользовательские директивы (Angular custom directive).

Angular директивы атрибуты

Создание самой простой директивы атрибута ограничивается классом, обернутым декоратором @Directive() с заданием необходимой конфигурации.

zoom.directive.ts (version 1)

@Directive({
  selector: '[zoom]'
})
export class ZoomDirective {}

Квадратные скобки являются указателем того, что это именно директива атрибут.

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

Для манипуляции элементом используется класс ElementRef из модуля @angular/core. Его свойство nativeElement предоставляет доступ к элементу по ссылке.

zoom.directive.ts (version 2)

@Directive({
  selector: '[zoom]'
})
export class ZoomDirective {
  constructor(private el: ElementRef) {
    el.nativeElement.style.fontSize = '20px'
  }
}

Angular директива из примера предназначена в основном для тега <p>, исходя из условия, что размер шрифта по умолчанию равен 14px, она увеличивает это значение до 20px.

<p>This text is normal.</p>
<p zoom>This text is larger.</p>

Сейчас zoom меняет только стилизацию элемента. Для того чтобы изменить поведение по умолчанию, используется декоратор @HostListener(). Теперь сделаем так, чтобы размер шрифта увеличивался только при наведении на элемент DOM-дерева курсора мыши. Иначе говоря, изменим стандартное поведение при возникновении пользовательского события.

@HostListener() также входит в состав @angular/core.

zoom.directive.ts (version 3)

@Directive({
  selector: '[zoom]'
})
export class ZoomDirective {
  constructor(private el: ElementRef) {}

  @HostListener('mouseenter') onMouseEnter() {
    this.setFontSize(20)
  }

  @HostListener('mouseleave') onMouseLeave() {
    this.setFontSize(14)
  }

  setFontSize(size: number | string): void {
    this.el.nativeElement.style.fontSize = `${size}px`
  }
}

@HostListener() привязывает обработчики к событиям, возникающим по отношению к элементу с Angular директивой.

Но что, если поведение элемента, задаваемое ему директивой, зависит от значения внешнего фактора? Рассмотрим передачу внешних значений.

Делается это с использованием входных свойств.

zoom.directive.ts (version 4)

@Directive({
  selector: '[zoom]'
})
export class ZoomDirective {
  @Input('zoomSize') size

  constructor(private el: ElementRef) {}

  @HostListener('mouseenter') onMouseIn() {
    this.setFontSize(this.size)
  }

  @HostListener('mouseleave') onMouseOut() {
    this.setFontSize(14)
  }

  setFontSize(value: number | string): void {
    this.el.nativeElement.style.fontSize = `${value}px`
  }
}

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

<p zoom [zoomSize]="20">Hover text to make it larger.</p>

Здесь размер шрифта, который будет установлен при наведении курсора мыши, задается через входное свойство zoomSize.

Чтобы не вводить лишнее свойство, можно сделать саму директиву zoom входным параметром.

//
export class ZoomDirective {
  @Input('zoom') size
  //
}
<p [zoom]="20">Hover text to make it larger.</p>

Структурные Angular директивы

Основное отличие структурных директив - они видоизменяют DOM-структуру страницы.

Отличительная их особенность - наличие перед ними символа *.

Префикс * лишь облегчает применение структурных директив, транслируя атрибут в <ng-template></ng-template>, служащий оберткой для элемента, к которому изначально была применена директива.

Например, запись

<p *ngIf="true">Some text</p>

транслируется в

<ng-template [ngIf]="true">
  <p>Some text</p>
</ng-template>

Создадим Angular директиву *duplicateContent для создания копии элемента в зависимости от истинности переданного выражения.

duplicate-content.directive.ts

@Directive({
  selector: '[duplicateContent]'
})
export class DuplicateContentDirective {
  @Input() set duplicateContent(condition: boolean) {
    if (condition && !this.contentWasDuplicated) {
      this.vc.insert(this.tpl)
      this.contentWasDuplicated = true
    }
  }

  private contentWasDuplicated: boolean = false

  constructor(private tpl: TemplateRef<any>, private vc: ViewContainerRef) {}
}

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

<div *duplicateContent="true">Content for duplication</div>

Создается структурная директива с применением декоратора @Directive(). Он принимает объект, в свойстве selector которого указывается наименование директивы в квадратных скобках.

Представление элемента, включая его самого, хранится в переменной templateRef, являющейся экземпляром класса TemplateRef. Контейнер представлений (элемент <ng-template/>) представлен переменной viewContainer. Подробно о представлениях.

Имея доступ к этим свойствам можно легко манипулировать DOM-элементом.

Обратите внимание, что приватное свойство contentWasDuplicated ограничивает создание более, чем одной копии содержимого.