Angular IDPStrong Junior
Модулі / Точний layout і Change Detection / Як працює change detection
🔥 0

Модуль 3 · урок 1 з 11

Як працює change detection

Change detection — це процес, у якому Angular з'ясовує, що на екрані більше не відповідає даним, і перемальовує саме це. Поки застосунок маленький, він працює непомітно. Коли в таблиці зʼявляється тисяча рядків, від розуміння цього механізму залежить, буде інтерфейс живим чи вʼязким.

Що взагалі відбувається

Angular тримає дерево компонентів. Коли щось могло змінитись, він проходить це дерево згори вниз і для кожного компонента порівнює: те, що зараз у шаблоні, і те, що в класі. Де розійшлось — оновлює DOM.

App Header TaskList Sidebar TaskCard TaskCard Перевірка завжди йде від кореня вниз і за один прохід. Питання лише в тому, скільки вузлів у неї потрапить.
Один цикл перевірки — це обхід дерева згори вниз. Оптимізація полягає не в тому, щоб зробити обхід швидшим, а в тому, щоб вузлів у ньому було менше.

Що саме запускає перевірку

Тут історично є два режими, і на робочому проєкті ти зустрінеш обидва.

Режим із 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) і подібні;
  • async pipe отримав нове значення;
  • хтось явно викликав markForCheck().

Наслідок: сторонній setTimeout сам собою більше нічого не перемальовує. Це і плюс (нічого зайвого), і пастка — якщо оновлювати поле класу з колбека сторонньої бібліотеки, екран просто не зміниться.

Два режими поруч

З zone.jsZoneless
Що запускає перевіркуБудь-яка асинхронна подія в застосункуСигнали, події шаблону, 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 і сигнали потрібні, щоб у цей обхід потрапляло менше вузлів. Наступні три уроки — саме про це.