Модуль 3 · урок 4 з 11
ChangeDetectorRef
Коротка службова тема. ChangeDetectorRef дає ручне керування перевіркою.
Знати про нього треба, використовувати — майже ніколи. Саме тому урок починається з того,
у яких випадках він справді потрібен, а в яких є ознакою проблеми.
Три методи
import { ChangeDetectorRef, inject } from '@angular/core';
export class TaskList {
private cdr = inject(ChangeDetectorRef);
}
| Метод | Що робить | Коли |
|---|---|---|
markForCheck() | Позначає компонент і шлях до кореня як такі, що треба перевірити в наступному циклі | Дані змінились поза шаблоном: ручний subscribe, сторонній колбек |
detectChanges() | Перевіряє це піддерево прямо зараз, синхронно | Дуже рідко: інтеграція із зовнішнім кодом, який сам керує часом |
detach() / reattach() | Вимикає компонент із перевірки зовсім | Майже ніколи: віджет із власним циклом рендеру, канвас |
markForCheck на практиці
// класичний випадок: ручна підписка в компоненті з OnPush
export class TaskList {
private cdr = inject(ChangeDetectorRef);
tasks: Task[] = [];
ngOnInit(): void {
this.api.getTasks()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(tasks => {
this.tasks = tasks;
this.cdr.markForCheck(); // без цього екран лишиться порожнім
});
}
}
Обидва сучасні підходи прибирають markForCheck зовсім:
Сигнал: tasks = signal<Task[]>([]) — Angular сам знає, що шаблон його читає.
async pipe: tasks$ | async у шаблоні — пайп сам викликає
markForCheck усередині.
Тобто якщо в компоненті зʼявився markForCheck, це майже завжди означає, що
десь лишився ручний subscribe, який варто прибрати. У модулі 4 це буде окремою
вимогою: нуль ручних підписок.
Чим markForCheck відрізняється від detectChanges
this.cdr.markForCheck(); // «перевір мене, коли дійдеш» — асинхронно, у наступному циклі
this.cdr.detectChanges(); // «перевір мене негайно» — синхронно, прямо в цьому рядку
Практична різниця: markForCheck безпечний, бо вбудовується у звичайний хід речей.
detectChanges запускає перевірку посеред виконання коду — і саме звідси беруться
ExpressionChangedAfterItHasBeenCheckedError та подвійні рендери.
- Треба виміряти елемент одразу після оновлення даних.
- Інтеграція з бібліотекою, яка сама вирішує, коли малювати.
- У тестах — щоб примусово оновити fixture (модуль 8).
detectChanges()після кожної зміни поля.- Він у
setTimeout«щоб точно спрацювало». - Його додали, бо «з OnPush не оновлювалось» — насправді там мутація.
detach()заради продуктивності — майже завжди є простіше рішення.
Що казати ліду
«markForCheck позначає шлях до кореня й спрацює в наступному циклі,
detectChanges перевіряє піддерево синхронно прямо зараз. У новому коді ні того,
ні того майже не треба: сигнали й async pipe роблять це самі. Якщо
markForCheck усе ж зʼявився — це сигнал, що лишилась ручна підписка.»