8. Встроенные директивы
🎯 Встроенные директивы Angular
Заголовок раздела «🎯 Встроенные директивы Angular»Директивы — это суперспособности HTML 🦸. Они говорят Angular что делать с элементами DOM: прятать, повторять, менять стили. Думай о них как о специальных атрибутах с магией внутри.
🤔 Директивы vs Компоненты
Заголовок раздела «🤔 Директивы vs Компоненты»Компонент — это директива со своим шаблоном. Все компоненты — директивы, но не все директивы — компоненты.
| Тип | Что делает | Примеры |
|---|---|---|
| Структурные | Добавляют/удаляют DOM-элементы | *ngIf, *ngFor, *ngSwitch |
| Атрибутные | Меняют внешний вид / поведение | [ngClass], [ngStyle], [(ngModel)] |
| Компонентные | Создают DOM с шаблоном | <app-header>, <mat-button> |
🏗️ Структурные директивы
Заголовок раздела «🏗️ Структурные директивы»Структурные директивы изменяют структуру DOM — добавляют, удаляют или переставляют элементы. Они всегда начинаются со звёздочки *.
*ngIf — условный рендеринг
Заголовок раздела «*ngIf — условный рендеринг»// В шаблоне компонента@Component({ template: ` <!-- Простой *ngIf --> <div *ngIf="isLoggedIn"> Привет, {{ user.name }}! 👋 </div>
<!-- С else блоком --> <div *ngIf="isLoggedIn; else loginPrompt"> Вы вошли как {{ user.name }} </div> <ng-template #loginPrompt> <button (click)="login()">Войти</button> </ng-template>
<!-- С then и else --> <ng-container *ngIf="status === 'loaded'; then dataBlock; else loadingBlock"> </ng-container> <ng-template #dataBlock> <app-data /> </ng-template> <ng-template #loadingBlock> <app-spinner /> </ng-template> `})export class AppComponent { isLoggedIn = false; status: 'loading' | 'loaded' | 'error' = 'loading';}💡
ng-template— невидимый контейнер. Он НЕ рендерится в DOM. Angular использует его как «заготовку» для условных и цикличных шаблонов.
*ngFor — итерация по коллекции
Заголовок раздела «*ngFor — итерация по коллекции»@Component({ template: ` <ul> <li *ngFor="let item of items; let i = index; let first = first; let last = last; let even = even; let odd = odd; trackBy: trackById" [class.first-item]="first" [class.last-item]="last" [style.background]="even ? '#f0f0f0' : 'white'" > {{ i + 1 }}. {{ item.name }} <span *ngIf="first"> 🥇 Первый!</span> <span *ngIf="last"> 🏁 Последний</span> </li> </ul> `})export class ListComponent { items = [ { id: 1, name: 'Angular' }, { id: 2, name: 'TypeScript' }, { id: 3, name: 'RxJS' } ];
// trackBy — ключ производительности! 🔑 // Без него Angular перерендерит ВЕСЬ список при любом изменении trackById(index: number, item: { id: number }): number { return item.id; // Angular знает какой элемент изменился }}*ngSwitch — переключение шаблонов
Заголовок раздела «*ngSwitch — переключение шаблонов»@Component({ template: ` <div [ngSwitch]="currentView"> <app-list-view *ngSwitchCase="'list'" /> <app-grid-view *ngSwitchCase="'grid'" /> <app-table-view *ngSwitchCase="'table'" /> <app-default-view *ngSwitchDefault /> </div>
<div class="controls"> <button (click)="currentView = 'list'">📋 Список</button> <button (click)="currentView = 'grid'">🔲 Сетка</button> <button (click)="currentView = 'table'">📊 Таблица</button> </div> `})export class DashboardComponent { currentView: 'list' | 'grid' | 'table' = 'list';}🔍 Десахаризация: что прячется за звёздочкой *?
Заголовок раздела «🔍 Десахаризация: что прячется за звёздочкой *?»Звёздочка — синтаксический сахар. Angular автоматически разворачивает её в <ng-template>:
<!-- Что пишем мы --><div *ngIf="condition">Контент</div>
<!-- Что Angular создаёт внутри --><ng-template [ngIf]="condition"> <div>Контент</div></ng-template><!-- *ngFor тоже разворачивается --><li *ngFor="let item of items">{{ item }}</li>
<!-- В этот ng-template --><ng-template ngFor let-item [ngForOf]="items"> <li>{{ item }}</li></ng-template>Понимание десахаризации помогает отлаживать сложные случаи с несколькими директивами на одном элементе 🕵️
✨ Новый синтаксис Angular 17+: @if, @for, @switch
Заголовок раздела «✨ Новый синтаксис Angular 17+: @if, @for, @switch»Angular 17 ввёл встроенные управляющие конструкции — они быстрее, типобезопаснее и не требуют CommonModule:
<!-- @if вместо *ngIf — поддерживает else if! -->@if (isLoggedIn) { <div>Добро пожаловать, {{ user.name }}!</div>} @else if (isLoading) { <app-spinner />} @else { <button (click)="login()">Войти</button>}<!-- @for вместо *ngFor — track ОБЯЗАТЕЛЕН -->@for (item of items; track item.id) { <li>{{ item.name }}</li>} @empty { <!-- Показывается если список пустой! --> <li>Список пуст 😢</li>}<!-- @switch вместо *ngSwitch -->@switch (status) { @case ('loading') { <app-spinner /> } @case ('error') { <app-error-message /> } @case ('empty') { <app-empty-state /> } @default { <app-content /> }}🚀 Почему новый синтаксис лучше?
- Компилируется в более эффективный код
- Не нужен импорт
CommonModule/NgIf/NgFor- IDE лучше понимает типы внутри блоков
@emptyв@for— killer-фича которой не было в*ngFor
🎨 Атрибутные директивы
Заголовок раздела «🎨 Атрибутные директивы»Атрибутные директивы не меняют структуру DOM — они меняют внешний вид или поведение существующих элементов.
[ngClass] — динамические CSS классы
Заголовок раздела «[ngClass] — динамические CSS классы»@Component({ template: ` <!-- Объект: имя класса -> условие --> <div [ngClass]="{ 'active': isActive, 'disabled': isDisabled, 'highlight': count > 5 }"> Динамические классы </div>
<!-- Массив классов --> <div [ngClass]="['base-style', themeClass, sizeClass]"> Несколько классов сразу </div>
<!-- Тернарный оператор --> <button [ngClass]="isActive ? 'btn btn-primary' : 'btn btn-secondary'"> Кнопка </button>
<!-- Метод компонента --> <div [ngClass]="getStatusClasses()"> Статус: {{ status }} </div> `})export class StyleDemoComponent { isActive = true; isDisabled = false; count = 7; themeClass = 'dark-theme'; sizeClass = 'size-lg'; status: 'online' | 'offline' | 'busy' = 'online';
getStatusClasses(): Record<string, boolean> { return { 'status-badge': true, 'status-online': this.status === 'online', 'status-offline': this.status === 'offline', 'status-busy': this.status === 'busy', }; }}[ngStyle] — динамические inline стили
Заголовок раздела «[ngStyle] — динамические inline стили»@Component({ template: ` <!-- Объект стилей --> <p [ngStyle]="{ 'color': textColor, 'font-size': fontSize + 'px', 'font-weight': isBold ? 'bold' : 'normal', 'opacity': isVisible ? 1 : 0, 'transition': 'all 0.3s ease' }"> Стилизованный текст </p>
<!-- Управление через контролы --> <input type="range" [(ngModel)]="fontSize" min="12" max="32"> <input type="color" [(ngModel)]="textColor"> <input type="checkbox" [(ngModel)]="isBold"> Жирный `})export class DynamicStyleComponent { textColor = '#dd0031'; fontSize = 16; isBold = false; isVisible = true;}⚡ trackBy — ключ производительности
Заголовок раздела «⚡ trackBy — ключ производительности»<!-- ❌ БЕЗ trackBy: Angular пересоздаёт ВСЕ DOM-элементы при любом изменении массива --><li *ngFor="let user of users">{{ user.name }}</li>
<!-- ✅ С trackBy: Angular обновляет ТОЛЬКО изменившиеся элементы --><li *ngFor="let user of users; trackBy: trackByUserId">{{ user.name }}</li>@Component({ changeDetection: ChangeDetectionStrategy.OnPush, // ещё +50% к скорости! template: ` <li *ngFor="let item of items$ | async; trackBy: trackById"> {{ item.name }} </li> `})export class OptimizedListComponent { items$ = this.store.select(selectItems);
// Функция должна возвращать уникальный идентификатор trackById = (_index: number, item: Item): number => item.id;
constructor(private store: Store) {}}🎯 Правило большого пальца: Всегда используй
trackByдля динамических списков. Без него Angular удаляет и пересоздаёт все DOM-узлы при каждом изменении массива — даже если изменился только один элемент.
📊 Сравнение *ngFor и @for
Заголовок раздела «📊 Сравнение *ngFor и @for»| Возможность | *ngFor | @for (Angular 17+) |
|---|---|---|
| Синтаксис | *ngFor="let i of items" | @for (i of items; track i.id) |
| track обязателен | Нет | Да ✅ |
| Пустой список | Нет встроенного | @empty {} блок |
| Производительность | Хорошая | Лучше |
| Нужен импорт | NgFor / CommonModule | Нет |