Модуль 3 · урок 1 з 11
Як працює change detection
Change detection — це процес, у якому Angular з'ясовує, що на екрані більше не відповідає даним, і перемальовує саме це. Поки застосунок маленький, він працює непомітно. Коли в таблиці зʼявляється тисяча рядків, від розуміння цього механізму залежить, буде інтерфейс живим чи вʼязким.
Що взагалі відбувається
Angular тримає дерево компонентів. Коли щось могло змінитись, він проходить це дерево згори вниз і для кожного компонента порівнює: те, що зараз у шаблоні, і те, що в класі. Де розійшлось — оновлює DOM.
Що саме запускає перевірку
Тут історично є два режими, і на робочому проєкті ти зустрінеш обидва.
Режим із zone.js — класика
Angular підміняє браузерні API: setTimeout, addEventListener,
fetch, XMLHttpRequest і десятки інших. Щойно щось із цього спрацювало,
zone.js каже: «сталась подія, дані могли змінитись» — і Angular запускає перевірку
всього дерева.
// кожен із цих рядків призведе до перевірки всього застосунку
setTimeout(() => {}, 0);
element.addEventListener('mousemove', () => {});
this.http.get('/api/tasks').subscribe();
Слухач mousemove без жодної логіки всередині все одно запускає повний обхід
дерева — десятки разів на секунду, поки миша рухається. Саме тому в старих Angular-застосунках
інтерфейс «важчав» під час скролу чи перетягування.
Zoneless — сучасний режим
// app.config.ts
providers: [provideZonelessChangeDetection()]
Тут zone.js прибирають зовсім. Angular більше не здогадується, що «щось могло статись», а покладається на явні сигнали:
- змінився сигнал, який читається в шаблоні;
- спрацював обробник події з шаблону —
(click)і подібні; asyncpipe отримав нове значення;- хтось явно викликав
markForCheck().
Наслідок: сторонній setTimeout сам собою більше нічого не перемальовує.
Це і плюс (нічого зайвого), і пастка — якщо оновлювати поле класу з колбека сторонньої
бібліотеки, екран просто не зміниться.
Два режими поруч
| З zone.js | Zoneless | |
|---|---|---|
| Що запускає перевірку | Будь-яка асинхронна подія в застосунку | Сигнали, події шаблону, async pipe, markForCheck |
| Обсяг перевірки | Усе дерево, якщо не обмежити через OnPush | Лише позначені гілки |
| Розмір бандла | Плюс zone.js | Менший |
| Ризик | Зайві перевірки | Забути про оновлення після стороннього колбека |
| Що робити | Ставити OnPush скрізь | Тримати стан у сигналах |
Помилка, яку побачить кожен
ExpressionChangedAfterItHasBeenCheckedError
Дослівно: «вираз змінився після того, як його вже перевірили». У режимі розробки Angular проходить дерево двічі й порівнює результати. Якщо за другий прохід значення стало іншим, це означає, що якийсь код змінює дані під час самої перевірки — і на екрані міг би зʼявитись проміжний стан.
// ❌ типова причина: батько міняє своє поле з колбека дитини під час перевірки
<app-child (ready)="this.isReady = true" />
// ✅ рішення 1: перенести зміну в асинхронний контекст
onReady(): void {
queueMicrotask(() => this.isReady.set(true));
}
// ✅ рішення 2 (краще): тримати значення в сигналі й не міняти його під час рендеру
У продакшн-збірці другого проходу немає, тому помилка не зʼявляється — але проблема лишається. Це не «дивна помилка Angular», а корисне попередження: дані змінюються не там, де слід.
Достатньо трьох речей: перевірка йде деревом згори вниз; у класичному режимі її запускає будь-яка асинхронна подія через zone.js; OnPush і сигнали потрібні, щоб у цей обхід потрапляло менше вузлів. Наступні три уроки — саме про це.