Как создать кастомную директиву?angular-55

Кастомные директивы в Angular позволяют добавлять собственное поведение к элементам DOM. Рассмотрим процесс создания как атрибутных, так и структурных директив.

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

Атрибутные директивы изменяют внешний вид или поведение элемента.

Шаг 1: Генерация директивы

ng generate directive highlight

Шаг 2: Реализация директивы

import { Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @Input() appHighlight = ''; // Цвет через входное свойство
  @Input() defaultColor = 'yellow'; // Значение по умолчанию

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

  @HostListener('mouseenter') onMouseEnter() {
    this.highlight(this.appHighlight || this.defaultColor);
  }

  @HostListener('mouseleave') onMouseLeave() {
    this.highlight('');
  }

  private highlight(color: string) {
    this.renderer.setStyle(
      this.el.nativeElement,
      'backgroundColor',
      color
    );
  }
}

Шаг 3: Использование директивы

<p [appHighlight]="'lightblue'" defaultColor="pink">
  Наведи на меня курсор
</p>

2. Создание структурной директивы

Структурные директивы изменяют структуру DOM.

Шаг 1: Реализация директивы

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appUnless]'
})
export class UnlessDirective {
  private hasView = false;

  @Input() set appUnless(condition: boolean) {
    if (!condition && !this.hasView) {
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.hasView = true;
    } else if (condition && this.hasView) {
      this.viewContainer.clear();
      this.hasView = false;
    }
  }

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef
  ) {}
}

Шаг 2: Использование директивы

<div *appUnless="isHidden">
  Этот блок виден, если isHidden = false
</div>

3. Продвинутые техники

Доступ к родительскому компоненту

import { Directive, Optional, Host } from '@angular/core';
import { SomeComponent } from './some.component';

@Directive({
  selector: '[appChild]'
})
export class ChildDirective {
  constructor(
    @Optional() @Host() private parent: SomeComponent
  ) {
    if (parent) {
      parent.registerChild(this);
    }
  }
}

Работа с контентом

@Directive({
  selector: '[appTooltip]'
})
export class TooltipDirective {
  @Input() appTooltip = '';

  private tooltipElement!: HTMLElement;

  @HostListener('mouseenter') onMouseEnter() {
    this.showTooltip();
  }

  private showTooltip() {
    this.tooltipElement = document.createElement('div');
    this.tooltipElement.textContent = this.appTooltip;
    // Добавляем стили и позиционирование
    document.body.appendChild(this.tooltipElement);
  }
}

4. Лучшие практики

  1. Используйте Renderer2 вместо прямого доступа к nativeElement
  2. Очищайте ресурсы в ngOnDestroy для директив с подписками
  3. Избегайте сложной логики в директивах
  4. Добавляйте префикс к селектору (appHighlight)
  5. Используйте @HostListener для обработки событий
  6. Документируйте директиву с помощью комментариев

5. Тестирование директивы

Пример теста для HighlightDirective:

describe('HighlightDirective', () => {
  let fixture: ComponentFixture<TestComponent>;
  let paragraph: DebugElement;

  beforeEach(() => {
    fixture = TestBed.configureTestingModule({
      declarations: [HighlightDirective, TestComponent]
    }).createComponent(TestComponent);

    paragraph = fixture.debugElement.query(By.css('p'));
    fixture.detectChanges();
  });

  it('should change background color on mouseenter', () => {
    paragraph.triggerEventHandler('mouseenter', null);
    expect(paragraph.nativeElement.style.backgroundColor).toBe('lightblue');
  });
});

@Component({
  template: `<p [appHighlight]="'lightblue'">Test</p>`
})
class TestComponent {}

Резюмируем

создание кастомных директив в Angular включает генерацию класса с декоратором @Directive, реализацию необходимой логики и регистрацию в модуле. Атрибутные директивы модифицируют элементы, а структурные — изменяют DOM. Использование Renderer2, HostListener и правильное управление ресурсами — ключ к созданию эффективных директив.