Angular
Жизненный цикл и afterRender
Команда переносит виджет графиков на Angular и в ngOnInit обращается к canvas, чтобы инициализировать библиотеку рисования. На сервере при SSR код падает: document не существует, а сам элемент ещё не отрисован. Так выясняется главное правило про жизненный цикл - момент, когда компонент создан, и момент, когда его DOM реально нарисован браузером, это разные точки во времени. Angular 21 дал для этого отдельный инструмент: afterRender и afterNextRender, которые срабатывают именно тогда, когда DOM готов, и только в браузере.
- Графические библиотеки (Chart.js, D3, three.js): инициализация требует реального DOM-элемента, а не момента создания компонента
- SSR и Angular Universal: код в ngOnInit выполняется на сервере, где нет document и window, поэтому работа с DOM выносится в afterNextRender
- Очистка ресурсов: подписки, таймеры и WebSocket-соединения освобождаются в ngOnDestroy, иначе утечки памяти при навигации между страницами
- Измерение размеров (getBoundingClientRect), фокус на поле ввода, интеграция с не-Angular кодом - всё это работа после отрисовки
- Миграция на сигналы: проекты постепенно убирают ngOnChanges в пользу input() и computed, упрощая компоненты
Предварительные знания
- Понимание, что такое компонент Angular и как он создаётся
- Базовое знание привязки данных и входных свойств
- Идея о серверном рендеринге (SSR): часть кода выполняется без браузера
Зачем понадобились afterRender и afterNextRender
До Angular 16 у разработчиков не было официального хука, который гарантировал бы, что весь DOM приложения отрисован. Хук ngAfterViewInit срабатывал после инициализации представления одного компонента, но не давал гарантии про всю страницу, а при SSR выполнялся и на сервере. Команда Angular ввела afterRender и afterNextRender как часть движения к сигналам и zoneless. Эти колбэки выполняются только в браузере после того, как Angular закончил отрисовку, что сделало работу с DOM предсказуемой и безопасной для серверного рендеринга. К Angular 21, где zoneless стал стандартом, эта пара колбэков заняла место главного инструмента для манипуляций с реальным DOM.
Хуки жизненного цикла: от создания до уничтожения
Компонент Angular проходит фиксированную последовательность этапов: Angular создаёт его экземпляр, устанавливает входные свойства, отрисовывает шаблон, обновляет его при изменениях и в конце удаляет. На ключевых этапах фреймворк вызывает методы компонента, если они объявлены. Эти методы и называют хуками жизненного цикла. Каждый хук объявлен в отдельном интерфейсе (OnInit, OnChanges, OnDestroy и так далее), что помогает не ошибиться в имени метода.
| Хук | Когда вызывается | Типичное применение |
|---|---|---|
| ngOnChanges | При установке и каждом изменении входных свойств | Реакция на новое значение @Input, пересчёт зависимых данных |
| ngOnInit | Один раз после первого ngOnChanges | Начальная загрузка данных, настройка компонента |
| ngDoCheck | При каждом цикле обнаружения изменений | Ручная проверка изменений, которые Angular не ловит сам |
| ngAfterViewInit | После инициализации представления компонента | Доступ к дочерним элементам через ViewChild |
| ngOnDestroy | Перед удалением компонента | Отписка, остановка таймеров, освобождение ресурсов |
Самая частая утечка памяти в Angular - подписка без отписки. Если ресурс создан в ngOnInit, его освобождение в ngOnDestroy обязательно. При навигации между страницами старые компоненты удаляются, и забытая подписка продолжает работать в фоне.
Чем ngOnInit отличается от ngOnChanges по частоте вызова?
Работа с DOM: afterRender и afterNextRender
Момент создания компонента и момент, когда браузер реально нарисовал его DOM, разнесены во времени. Хуки вроде ngOnInit срабатывают рано, когда элементов на странице ещё может не быть, а при серверном рендеринге выполняются в среде без document и window. Для работы с реальным DOM Angular даёт два колбэка: afterNextRender выполняется один раз после следующей отрисовки, afterRender - после каждой. Оба работают только в браузере, поэтому код внутри них безопасен для SSR.
- afterNextRender — Срабатывает один раз после ближайшей отрисовки. Подходит для одноразовой инициализации: создание графика, установка фокуса, замер размеров
- afterRender — Срабатывает после каждой отрисовки. Используется реже и осторожно, например для постоянной синхронизации с внешней DOM-библиотекой. Тяжёлая работа здесь бьёт по производительности
afterRender и afterNextRender вызываются в контексте инъекции, поэтому их регистрируют в конструкторе или в фабрике провайдера. Внутри доступен inject, а сами колбэки автоматически прекращают работу при удалении компонента.
Почему инициализацию графической библиотеки, которой нужен canvas, лучше делать в afterNextRender, а не в ngOnInit?
Как сигналы сократили опору на хуки
Раньше реакция на изменение входного свойства писалась в ngOnChanges: сравнить старое и новое значение, пересчитать зависимое поле. В Angular 21 входное свойство объявляют через input(), и оно становится сигналом. Производные значения выражают через computed, а побочные эффекты - через effect. В результате логика, которая занимала несколько хуков, превращается в пару декларативных строк, а компонент перестаёт зависеть от ручного жизненного цикла.
- ngOnChanges для пересчёта производных значений заменяется на computed
- Реактивные побочные эффекты переезжают из ngOnInit и ngDoCheck в effect
- Доступ к дочерним элементам идёт через сигнальный viewChild вместо ngAfterViewInit и декоратора ViewChild
- Отписку от RxJS-потоков берёт на себя takeUntilDestroyed вместо ручного ngOnDestroy
Хуки никуда не исчезли и остаются валидными. ngOnDestroy по-прежнему нужен для ресурсов вне реактивной системы, а afterNextRender - для прямой работы с DOM. Сигналы убирают именно рутину вокруг производного состояния и реактивных эффектов.
Какой хук жизненного цикла чаще всего становится лишним при переходе на computed для производных значений?
Связь с другими темами
Жизненный цикл - мост между классическим Angular и сигнальной моделью. Дальше курс раскрывает реактивную часть:
- Сигналы — signal и computed снимают часть работы с ngOnChanges и ngDoCheck, делая компонент короче
- effect — effect берёт на себя реактивные побочные эффекты, которые раньше писали в хуках вручную
- Сигнальные запросы — viewChild возвращает сигнал, а afterNextRender даёт момент, когда к этому DOM-элементу можно безопасно обратиться
Итог
- Жизненный цикл компонента - это последовательность моментов от создания до уничтожения, для каждого есть свой хук
- ngOnInit вызывается один раз после установки входных свойств, ngOnChanges - при каждом изменении входов, ngOnDestroy - перед удалением компонента
- Создание компонента и отрисовка его DOM - разные моменты, поэтому прямая работа с DOM в ngOnInit ненадёжна
- afterRender и afterNextRender выполняются только в браузере после отрисовки, что делает их безопасными для SSR и подходящими для интеграции с DOM-библиотеками
- Сигналы (signal, computed, effect) в Angular 21 переносят реактивную логику из хуков в декларативные конструкции, сокращая опору на жизненный цикл
Связанные уроки
- ng-11-signals-intro — Сигналы убирают часть работы, которая раньше держалась на ngOnChanges и ручных проверках. Логичный следующий шаг
- ng-13-effect — effect перенимает роль реактивных побочных эффектов, которые раньше писали в ngOnInit и ngDoCheck
- ng-15-signal-queries — afterNextRender часто используется вместе с сигнальными viewChild для доступа к DOM после первого рендера