Создание пользовательских директив

Директивы в Angular — это основные строительные блоки, которые позволяют расширять HTML с помощью дополнительного функционала. Они дают возможность разработчикам изменять или добавлять поведение элементов DOM, а также создавать абстракции для повторно используемых компонентов. В этой части статьи рассматриваются основные принципы создания и использования пользовательских директив в Angular.

Типы директив

В Angular существует несколько типов директив:

  1. Структурные директивы изменяют структуру DOM. Пример: *ngIf, *ngFor.
  2. Атрибутные директивы изменяют внешний вид или поведение элементов. Пример: ngClass, ngStyle.
  3. Компоненты также являются разновидностью директив, но они включают шаблон и логику, что отличает их от обычных директив.

Для создания пользовательских директив в Angular используется @Directive декоратор.

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

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

  1. Создание файла директивы:

    Создадим файл с директивой, например, highlight.directive.ts.

    import { Directive, ElementRef, Renderer2 } from '@angular/core';
    
    @Directive({
      selector: '[appHighlight]'
    })
    export class HighlightDirective {
    
      constructor(private el: ElementRef, private renderer: Renderer2) { }
    
      ngOnInit() {
        this.renderer.setStyle(this.el.nativeElement, 'background-color', 'yellow');
      }
    }

    В этом примере создаётся директива, которая изменяет цвет фона элемента на жёлтый.

  2. Пояснение к коду:

    • @Directive({ selector: '[appHighlight]' }) — это метаданные директивы. Она будет применяться к любому элементу, у которого есть атрибут appHighlight.
    • В конструкторе директивы используется ElementRef, который предоставляет доступ к элементу DOM, и Renderer2, который позволяет безопасно изменять его стили.
    • В методе ngOnInit() применяется изменение стиля через renderer.setStyle(). Это позволяет избежать прямого доступа к DOM, что важно для безопасности и совместимости с серверным рендерингом.
  3. Добавление директивы в модуль:

    Чтобы директива начала работать, её необходимо добавить в декоратор @NgModule в разделе declarations.

    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { HighlightDirective } from './highlight.directive';
    
    @NgModule({
      declarations: [HighlightDirective],
      imports: [CommonModule],
      exports: [HighlightDirective]
    })
    export class SharedModule { }
  4. Использование директивы в шаблоне:

    Теперь директива доступна для использования в шаблоне Angular-компонента. Достаточно добавить её к любому элементу в HTML-шаблоне.

    <div appHighlight>
      Этот текст будет выделен жёлтым фоном.
    </div>

Директивы с входными параметрами

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

  1. Модификация директивы для поддержки входных параметров:

    Для этого используется декоратор @Input(), который позволяет передавать данные в директиву.

    import { Directive, ElementRef, Renderer2, Input } from '@angular/core';
    
    @Directive({
      selector: '[appHighlight]'
    })
    export class HighlightDirective {
    
      @Input() appHighlight: string;
    
      constructor(private el: ElementRef, private renderer: Renderer2) { }
    
      ngOnInit() {
        const color = this.appHighlight || 'yellow'; // Если не передан цвет, используем желтый
        this.renderer.setStyle(this.el.nativeElement, 'background-color', color);
      }
    }
  2. Использование директивы с параметром:

    В HTML-шаблоне теперь можно передавать значение цвета:

    <div [appHighlight]="'lightblue'">
      Этот текст будет выделен светло-голубым фоном.
    </div>

Директивы с выходами (Events)

В Angular директивы могут также генерировать события. Для этого используется декоратор @Output() и класс EventEmitter.

  1. Пример директивы с выходом:

    Пусть директива будет изменять цвет фона при наведении и при этом отправлять событие.

    import { Directive, ElementRef, Renderer2, HostListener, Output, EventEmitter } from '@angular/core';
    
    @Directive({
      selector: '[appHover]'
    })
    export class HoverDirective {
    
      @Output() hoverChanged = new EventEmitter<boolean>();
    
      constructor(private el: ElementRef, private renderer: Renderer2) { }
    
      @HostListener('mouseenter') onMouseEnter() {
        this.renderer.setStyle(this.el.nativeElement, 'background-color', 'orange');
        this.hoverChanged.emit(true);
      }
    
      @HostListener('mouseleave') onMouseLeave() {
        this.renderer.setStyle(this.el.nativeElement, 'background-color', 'transparent');
        this.hoverChanged.emit(false);
      }
    }

    В этом примере директива генерирует событие hoverChanged при наведении и убирании мыши с элемента. С помощью HostListener отслеживаются события mouseenter и mouseleave.

  2. Использование директивы с событием:

    В шаблоне можно подписаться на это событие.

    <div appHover (hoverChanged)="onHoverChanged($event)">
      Наведите курсор, чтобы изменить цвет фона.
    </div>

    В методе компонента onHoverChanged можно обработать событие:

    export class AppComponent {
      onHoverChanged(isHovered: boolean) {
        console.log(isHovered ? 'Элемент наведён' : 'Элемент покинут');
      }
    }

Директивы с жизненным циклом

Как и компоненты, директивы в Angular имеют жизненный цикл, состоящий из различных хуков. Они позволяют отслеживать изменения состояния директивы и взаимодействовать с DOM. Некоторые из наиболее часто используемых хуков:

  1. ngOnInit() — вызывается один раз при инициализации директивы.
  2. ngOnChanges() — вызывается каждый раз при изменении входных данных.
  3. ngDoCheck() — вызывается при каждом цикле обнаружения изменений.
  4. ngOnDestroy() — вызывается при уничтожении директивы.

Пример использования жизненного цикла директивы:

import { Directive, ElementRef, Renderer2, OnInit, OnDestroy } from '@angular/core';

@Directive({
  selector: '[appLog]'
})
export class LogDirective implements OnInit, OnDestroy {

  constructor(private el: ElementRef, private renderer: Renderer2) { }

  ngOnInit() {
    console.log('Директива инициализирована');
  }

  ngOnDestroy() {
    console.log('Директива уничтожена');
  }
}

Регистрация директивы в модуле

Все пользовательские директивы должны быть зарегистрированы в модуле, чтобы их можно было использовать в компонентах. Для этого необходимо добавить директиву в массив declarations в @NgModule.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LogDirective } from './log.directive';

@NgModule({
  declarations: [LogDirective],
  imports: [CommonModule],
  exports: [LogDirective]
})
export class SharedModule { }

Заключение

Создание пользовательских директив в Angular предоставляет разработчикам мощный инструмент для манипуляции DOM и создания динамичных веб-приложений. Директивы позволяют добавлять новые функциональности и улучшать интерфейсы без необходимости писать много повторяющегося кода. Применяя директивы с входными параметрами, событиями и различными жизненными циклами, можно добиться высокой гибкости и повторного использования кода.