Angular IDPStrong Junior
🔥 0

Модуль 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 та подвійні рендери.

Виправдані випадки detectChanges
  • Треба виміряти елемент одразу після оновлення даних.
  • Інтеграція з бібліотекою, яка сама вирішує, коли малювати.
  • У тестах — щоб примусово оновити fixture (модуль 8).
Ознаки, що це милиця
  • detectChanges() після кожної зміни поля.
  • Він у setTimeout «щоб точно спрацювало».
  • Його додали, бо «з OnPush не оновлювалось» — насправді там мутація.
  • detach() заради продуктивності — майже завжди є простіше рішення.

Що казати ліду

«markForCheck позначає шлях до кореня й спрацює в наступному циклі, detectChanges перевіряє піддерево синхронно прямо зараз. У новому коді ні того, ні того майже не треба: сигнали й async pipe роблять це самі. Якщо markForCheck усе ж зʼявився — це сигнал, що лишилась ручна підписка.»