10 — Встроенные директивы на пальцах
Цель: пройтись по всем готовым директивам, которые даёт Angular «из коробки», понять
что они делают и как связаны с механикой view/карманов. Точные пути в
исходнике — в соседнем vault'е (notes/v21/).
Сначала — два слова про «новый» и «старый» способ
В Angular 21 у управления шаблоном есть два поколения:
НОВОЕ (control flow, встроено в компилятор — это НЕ директивы):
@if / @else if / @else
@for (... ; track ...) { } @empty { }
@switch / @case / @default
СТАРОЕ (директивы из @angular/common, теперь @deprecated):
*ngIf *ngFor *ngSwitch / *ngSwitchCase / *ngSwitchDefault
Важно: @if/@for/@switch — это синтаксис компилятора, а не директивы. Их не
надо импортировать, они компилируются в специальные инструкции (быстрее и проще
типизируются). Старые *ngIf/*ngFor — это настоящие классы-директивы, которые под
капотом делают то же самое (createEmbeddedView / remove), но через
ViewContainerRef руками. Подробно механизм — в 08-portaly.
Практика: в новом коде пиши
@if/@for/@switch. Старые*ng*ещё работают, но их планируют убрать. Дальше в главе — что под капотом у старых, чтобы понимать легаси-код.
Группа 1 — структурные (меняют структуру DOM)
«Структурная» = добавляет/убирает куски DOM. Узнаёшь по * (микросинтаксис). Все
работают через те самые три шага из 08-portaly: карман → копия view → вставить.
*ngIf — показать/спрятать
<div *ngIf="open">...</div>
<div *ngIf="user as u; else loading">{{u.name}}</div>
<ng-template #loading>Загрузка...</ng-template>
true→createEmbeddedViewтела;false→clear(убрать).ngIfThen/ngIfElse— какой шаблон показывать в каждой ветке.- Новый аналог:
@if (open) { } @else { }.
*ngFor — список
<li *ngFor="let n of items; track n; let i = index">{{i}}: {{n}}</li>
- Внутри —
IterableDiffer: на каждом цикле смотрит, что в массиве добавилось/удалилось/переехало, и делает только нужные правки (createEmbeddedView/move/remove) — список целиком не перерисовывается. trackBy(старый) /track(новый) — как опознавать элементы, чтобы не пересоздавать DOM зря.- Локальные переменные:
index,count,first,last,even,odd. - Новый аналог:
@for (n of items; track n) { } @empty { }.
*ngSwitch — выбор одной ветки
<div [ngSwitch]="status">
<p *ngSwitchCase="'ok'">Готово</p>
<p *ngSwitchCase="'err'">Ошибка</p>
<p *ngSwitchDefault>Ждём</p>
</div>
- Сравнивает значение (
===), показывает совпавший case (или default). - Новый аналог:
@switch (status) { @case ('ok') {} @default {} }.
Группа 2 — outlet'ы (вставить готовое в это место)
Это не «спрячь/покажи», а «возьми вот это и вставь сюда». Тонкие обёртки над движком из 08-portaly.
ngTemplateOutlet — вставь этот <ng-template>
<ng-template #row let-name="name"><b>{{name}}</b></ng-template>
<div *ngTemplateOutlet="row; context: { name: 'Аня' }"></div>
- Под капотом —
viewContainerRef.createEmbeddedView(tpl, context). - Хитрость: контекст передаётся через Proxy — поменял поле контекста, view не пересоздаётся, просто обновляется. Сменил сам шаблон — старый убирается, новый вставляется.
- Зачем: переиспользовать кусок разметки, передавать «слоты»-шаблоны в дочерний компонент.
ngComponentOutlet — создай компонент динамически
<ng-container *ngComponentOutlet="WidgetComponent; inputs: { title: 'Привет' }" />
- Под капотом —
viewContainerRef.createComponent(Comp, {...}). - Умеет: передавать
inputs(на каждом цикле синхронизирует черезcomponentRef.setInput()), свойinjector,environmentInjector,ngModule, content-проекцию. - Зачем: когда тип компонента известен только в рантайме (плагины, виджеты,
динамические формы) — декларативная замена ручного
createComponent.
Группа 3 — атрибутные (меняют сам элемент, не структуру)
Не добавляют/убирают DOM, а правят атрибуты существующего элемента. Структуры не трогают, карманы не создают.
ngClass — классы по условию
<div [ngClass]="{ active: isActive, error: hasError }"></div>
<div [ngClass]="['box', theme]"></div>
- Принимает строку / массив /
Set/ объект{ class: boolean }. - На каждом
ngDoCheckсверяет состояние каждого класса и черезRenderer2добавляет/убирает только то, что изменилось. - Часто проще: нативный
[class.active]="isActive"— для одного класса не нуженngClass.
ngStyle — inline-стили
<div [ngStyle]="{ 'color': c, 'top.px': y }"></div>
- Принимает объект
{ styleName: value }, понимает единицы в ключе (top.px,font-size.em). - Через
KeyValueDifferотслеживает изменения и правит стили черезRenderer2. - Часто проще: нативный
[style.color]="c"/[style.top.px]="y".
Бонус — i18n (это pipe'ы, не директивы)
i18nPlural— число → текст по правилам множественного числа CLDR ({ '=0': 'нет', '=1': 'один', other: '# штук' }).i18nSelect— выбор строки по значению ({ male: 'он', female: 'она', other: 'оно' }).
А где CDK Portal / Overlay / Dialog?
Их нет в этом исходнике (@angular/cdk — отдельная библиотека, не часть ядра).
В 08-portaly описано, что CDK Portal — лишь полиморфная обёртка над всё теми же
createEmbeddedView / createComponent / переносом DOM. Никакого нового механизма.
Карта: кто через что работает
СТРУКТУРНЫЕ (структура DOM) ← движок: ViewContainerRef + createEmbeddedView
*ngIf / @if → создать/убрать тело
*ngFor / @for → IterableDiffer → createEmbeddedView/move/remove
*ngSwitch / @switch → показать совпавший case
OUTLET'Ы (вставить готовое) ← тот же движок, тонкая обёртка
ngTemplateOutlet → createEmbeddedView(tpl, ctx)
ngComponentOutlet → createComponent(Comp, {inputs,...})
АТРИБУТНЫЕ (правят элемент) ← НЕ создают view, правят через Renderer2
ngClass → add/removeClass
ngStyle → set/removeStyle
Итог
- Сегодня по умолчанию:
@if/@for/@switch(control flow компилятора), а не*ngIf/*ngFor/*ngSwitch(старые директивы, deprecated). - Структурные директивы и
@for/@if— это обёртки над «карман + копия view» из 08-portaly. ngTemplateOutlet/ngComponentOutlet— «вставь готовый шаблон/компонент сюда».ngClass/ngStyle— единственные, кто не создаёт view, а просто правит атрибуты элемента.- DI, который раздают компоненты этим детям, ищется по дереву узлов — см. 09-di-injectory.
Теперь у тебя полная картина: код → компиляция → сборка → старт → работа →
view/LView → селекторы → порталы → DI → встроенные директивы. Открой
prosto.canvas — там весь путь одним полотном.