Модуль 2 · урок 9 з 15
Директиви
Директива — це поведінка, яку вішають на існуючий елемент. Компонент приносить свій шаблон, директива — ні: вона лише щось робить із тим, на чому висить. У таблиці ліда власна директива стоїть окремим пунктом, і це найшвидший спосіб показати, що ти розумієш Angular глибше за «намалював компонент».
Атрибутна директива
import { Directive, input, computed } from '@angular/core';
@Directive({
selector: '[appOverdue]', // вішається як атрибут
host: {
'[class.is-overdue]': 'overdue()', // клас за умовою
'[attr.aria-label]': 'label()',
},
})
export class OverdueDirective {
appOverdue = input.required<string | Date>(); // значення з того ж атрибута
private readonly overdue = computed(() => new Date(this.appOverdue()) < new Date());
private readonly label = computed(() => this.overdue() ? 'Прострочено' : null);
}
<tr [appOverdue]="task.dueDate">…</tr>
Об'єкт host — сучасна форма того, що раніше писали декораторами
@HostBinding і @HostListener. Обидва варіанти живі:
// класичний вигляд — його ти зустрінеш у чужому коді
@Directive({ selector: '[appHighlight]' })
export class HighlightDirective {
@HostBinding('class.is-hover') hovered = false;
@HostListener('mouseenter') onEnter() { this.hovered = true; }
@HostListener('mouseleave') onLeave() { this.hovered = false; }
}
Директива з доступом до елемента
import { Directive, ElementRef, inject, effect, input } from '@angular/core';
@Directive({ selector: '[appAutofocus]' })
export class AutofocusDirective {
enabled = input(true, { alias: 'appAutofocus' });
private el = inject(ElementRef<HTMLElement>);
constructor() {
effect(() => {
if (this.enabled()) this.el.nativeElement.focus();
});
}
}
<input appAutofocus>
<input [appAutofocus]="isFirstField()">
Ось приклад, коли директива справді доречна: поведінку «постав фокус» треба чіпляти до різних елементів, і робити заради неї компонент безглуздо. У модулі 7 ця директива знадобиться в модалці.
Структурна директива
Структурна директива керує тим, чи існує елемент у DOM. Саме такими були
*ngIf і *ngFor до появи @if і @for.
Писати свої тепер доводиться рідко, але один випадок лишився корисним — приховування
за правами доступу.
import { Directive, TemplateRef, ViewContainerRef, inject, input, effect } from '@angular/core';
import { AuthService } from '../../core/auth/auth.service';
@Directive({ selector: '[appIfRole]' })
export class IfRoleDirective {
appIfRole = input.required<string>();
private tpl = inject(TemplateRef<unknown>);
private vcr = inject(ViewContainerRef);
private auth = inject(AuthService);
private shown = false;
constructor() {
effect(() => {
const allowed = this.auth.hasRole(this.appIfRole());
if (allowed && !this.shown) {
this.vcr.createEmbeddedView(this.tpl); // вставити шматок шаблону
this.shown = true;
} else if (!allowed && this.shown) {
this.vcr.clear(); // прибрати з DOM
this.shown = false;
}
});
}
}
<button *appIfRole="'producer'" (click)="remove()">Видалити</button>
Зірочка перед іменем — це скорочення. Angular розгортає його в <ng-template>,
і саме тому директива отримує TemplateRef — заготовку розмітки, яку можна
вставити або не вставити.
Директива прав ховає кнопку з інтерфейсу, але не забороняє дію. Хто завгодно може викликати метод із консолі або смикнути ендпоїнт напряму. Тому приховування — це зручність, а справжній захист — на бекенді. Про це варто сказати вголос, якщо на демо зайде мова про ролі: це рівно та відповідь, яку хоче почути тімлід.
Компонент, директива чи пайп
| Питання | Відповідь |
|---|---|
| Потрібна власна розмітка? | Компонент |
| Треба додати поведінку до наявного елемента? | Атрибутна директива |
| Треба вирішувати, чи існує елемент у DOM? | Структурна директива — або просто @if |
| Треба лише відформатувати значення? | Пайп |
Для закриття пункту достатньо однієї атрибутної директиви, яку ти реально використовуєш.
Найкорисніші кандидати: підсвітка прострочених рядків у таблиці, автофокус у модалці,
або appIfRole для кнопок адміністратора. Головне — щоб вона була в ділі,
а не лежала «для галочки».