Модуль 2 · урок 3 з 15
Синтаксис шаблонів
Шаблон — це HTML із чотирма додатковими можливостями: вставити значення, передати дані в елемент, спіймати подію й керувати структурою. У сучасному Angular структурні конструкції стали частиною мови шаблонів, і це найпомітніша зміна порівняно з тим, що ти бачив у старих туторіалах.
Чотири види прив'язки
<h3>{{ task().title }}</h3> <!-- 1. інтерполяція: значення в текст -->
<input [value]="task().code"> <!-- 2. властивість елемента -->
<div [class.is-overdue]="isLate()"> <!-- клас за умовою -->
<div [style.width.%]="progress()"> <!-- стиль з одиницями -->
<button [disabled]="saving()"> <!-- атрибут-прапорець -->
<td [attr.colspan]="span()"> <!-- саме атрибут, а не властивість -->
<button (click)="save()"> <!-- 3. подія -->
<input (input)="onType($event)"> <!-- $event — об'єкт події -->
<form (submit)="submit()" (keydown.escape)="close()"> <!-- фільтр по клавіші -->
<app-search [(value)]="query"> <!-- 4. в обидва боки -->
[value] — це властивість DOM-об'єкта, [attr.value] — атрибут у HTML.
Здебільшого потрібна перша форма. Друга — коли властивості просто не існує:
colspan, aria-*, data-*. Якщо прив'язка «не працює
без причини» — спробуй attr., це класична пастка.
Керування структурою: @if, @for, @switch
@if (loading()) {
<app-skeleton />
} @else if (error()) {
<p class="error">{{ error() }}</p>
} @else {
<app-task-table [rows]="tasks()" />
}
@for (task of tasks(); track task.id) {
<app-task-card [task]="task" />
} @empty {
<p class="empty">Задач немає — саме час створити першу.</p>
}
Дві речі тут нові порівняно зі старим *ngFor:
trackобов'язковий. Angular більше не дозволяє про нього забути — і це добре, бо саме він визначає, чи перемалює браузер увесь список, чи лише змінений рядок.@emptyвбудований. Раніше порожній стан доводилось писати окремим*ngIf— а найчастіше про нього просто забували (модуль 7 про це нагадає).
@switch (task().status) {
@case ('done') { <span class="badge badge--done">Готово</span> }
@case ('blocked') { <span class="badge badge--blocked">Блок</span> }
@default { <span class="badge">В роботі</span> }
}
Чому track так важливий
track каже Angular, як упізнати той самий елемент після оновлення масиву.
Без нього кожне оновлення — це заново створені DOM-вузли.@let — локальна змінна в шаблоні
@let user = task().assignee;
@let isLate = task().dueDate < today();
<p>{{ user?.name ?? 'Не призначено' }}</p>
<span [class.late]="isLate">{{ task().dueDate | date:'dd.MM' }}</span>
Раніше для цього робили трюк *ngIf="expr as value". Тепер є нормальні змінні —
зручно, коли одне й те саме довге вираження повторюється в шаблоні кілька разів.
@defer — відкласти важке
@defer (on viewport) {
<app-heavy-chart [data]="stats()" />
} @placeholder {
<div class="chart-skeleton"></div>
} @loading (minimum 300ms) {
<app-spinner />
} @error {
<p>Не вдалось завантажити графік</p>
}
Код компонента всередині @defer потрапляє в окремий шматок бандла й вантажиться,
лише коли блок з'явиться у видимій області. Тригери бувають різні: on viewport,
on interaction, on hover, on timer(2s), when умова.
Для дашборду з важким графіком це найдешевша оптимізація, яка існує.
Старий синтаксис: що ти зустрінеш у чужому коді
| Старе | Нове | Різниця |
|---|---|---|
*ngIf="x" | @if (x) { } | Не потребує імпорту, підтримує @else if |
*ngFor="let t of list; trackBy: fn" | @for (t of list; track t.id) | track обов'язковий, є @empty |
[ngSwitch] | @switch | Читабельніше, без директив |
*ngIf="x as v" | @let v = x; | Змінна без обгортання в умову |
<ng-container> | здебільшого не потрібен | Нові блоки не додають зайвого елемента самі |
Старий синтаксис не «зламаний» і працює далі — на робочому проєкті ти майже напевно побачиш саме його. Але писати новий код варто в новому стилі, і на демо ліду це помітно.
У шаблоні не викликають методи: {{ calcTotal() }} виконується на кожній перевірці
змін — інколи сотні разів за секунду. Замість цього computed() у класі або пайп.
Це прямо пов'язано з модулем 3 і з вимогою про OnPush: метод у шаблоні зводить нанівець
усю оптимізацію.