04 — Runtime и change detection: клик меняет цифру

Цель: понять, что именно происходит между «ты нажал +1» и «на экране стало 1». Это самая важная глава.

Сначала — что такое сигнал

count = signal(0) — это коробочка со значением 0, за которой Angular следит.

  • Прочитать: count() → вернёт 0.
  • Поменять: count.set(5) или count.update(c => c + 1).

Фокус в том, что когда ты читаешь сигнал внутри шаблона ({{ count() }}), Angular запоминает: «этот кусок экрана зависит от этого сигнала». Поэтому при изменении сигнала он точно знает, что обновить.

doubled = computed(() => count() * 2) — это сигнал, который сам пересчитывается, когда меняется count. Тебе не надо его трогать руками.

Change detection = «проверить и обновить экран»

Change detection (CD) — процесс «пройтись по компонентам, посчитать свежие значения и вписать их в DOM». В Angular 21 он zoneless:

CD запускается ТОЛЬКО когда реально что-то изменилось (сигнал поменялся, пришло событие). Просто так, постоянно — не крутится.

Это отличается от старого Angular (zone.js), где CD дёргался после любого действия (любой setTimeout, любой клик где угодно). Подробнее про разницу → 05 — OnPush и zoneless.

Анимация: полный цикл одного клика

assets/cd-cycle.svg

То же самое покадрово (читай сверху вниз — это «кадры гифки»):

Кадр 1.  Ты жмёшь [ +1 ]
Кадр 2.  Срабатывает (click) → вызывается inc()
Кадр 3.  inc() делает count.update(0 → 1)
Кадр 4.  Сигнал count изменился → помечает свой <p> "грязным"
Кадр 5.  Angular планирует ОДНУ проверку (tick) — не сразу, а на ближайший микротакт
Кадр 6.  tick запускается:
            • computed doubled пересчитывается (0→2), items (пусто→[1])
            • template-функция в режиме ОБНОВИТЬ (rf & 2) проходит по компоненту
            • вписывает 1 в <p>Счётчик>, 2 в <p>Удвоенный>, рисует <li>1</li>
Кадр 7.  DOM обновлён. Экран:  Счётчик: 1 | Удвоенный: 2 | • 1

Ключевые моменты этого цикла

1. Меняется только нужное

template-функция в режиме «обновить» сравнивает старое значение с новым и трогает DOM только если значение поменялось. <h1>demo21</h1> не менялся — его никто не перерисовывает.

2. computed «бесплатные»

Тебе не надо обновлять doubled и items вручную. Они зависят от count, и при чтении в режиме «обновить» сами берут свежее значение. Поменял count — всё остальное подтянулось.

3. Одна проверка на всё

Если в одном обработчике ты поменяешь count три раза, Angular не запустит CD три раза. Он запланирует одну проверку и сделает её один раз. Это экономит работу.

4. Что НЕ запускает CD в zoneless

setTimeout(() => { this.x = 5; });   // x — обычное поле, не сигнал

Это НЕ обновит экран! Потому что x — не сигнал, Angular о нём не знает, и проверку никто не запланировал. Это главная ловушка при переходе на zoneless — см. главу 05. Чтобы заработало — сделай x сигналом.

Пример с порогом: count 2 → 3

Когда count переваливает за 2, в режиме «обновить» срабатывает @if (count() > 2), и блок «Больше двух!» создаётся прямо сейчас (вызывается режим «создать» для этого кусочка):

count = 2:   Счётчик: 2 | Удвоенный: 4 | • 1 • 2
   ↓ клик
count = 3:   Счётчик: 3 | Удвоенный: 6 | "Больше двух!" | • 1 • 2 • 3
                                          ↑ @if смонтировал блок
                                                            ↑ @for добавил <li>3

Видишь: @if/@for создают и удаляют куски DOM по ходу, точечно, ровно когда данные этого требуют. Это и есть «работа» приложения.

Мини-словарь этой главы

  • tick — один запуск проверки «обновить экран».
  • «грязный» (dirty) — пометка «этот компонент надо проверить».
  • zoneless — режим, где проверка запускается только от сигналов/событий, а не постоянно.

Дальше: когда именно Angular проверяет компонент, а когда пропускает (OnPush) → 05 — OnPush и zoneless.