Жизненные циклы

Вы когда-нибудь смотрели на код Angular и думали: «Почему ngOnInit вызывается первым? А где ngOnChanges? И куда пропало значение переменной?» Знакомо? Это как собирать мебель без инструкции — вроде все детали есть, а собрать ровно не получается. Жизненные циклы Angular — это та самая инструкция, которая объясняет, что и когда происходит внутри вашего компонента. И сегодня вы разберетесь в этом раз и навсегда.
Что такое жизненный цикл компонента и почему это не скучная теория
Жизненный цикл — это последовательность событий, которые Angular запускает автоматически: от создания компонента до его полного удаления из DOM. Каждое событие — это хук (hook), то есть специальный метод, который вы можете переопределить. Если вы знаете порядок этих хуков, вы сможете предсказать, когда загружать данные, когда подписываться на Observable, а когда — отписываться, чтобы не было утечек памяти.
Представьте, что вы строите дом. Сначала заливаете фундамент (constructor), потом возводите стены (ngOnInit), проводите электрику (ngAfterViewInit) и, наконец, сносите постройку (ngOnDestroy). Если вы начнете прокладывать провода до того, как стены готовы, получится хаос. Вот почему порядок хуков критичен.
Полный список хуков: от constructor до ngOnDestroy
В Angular есть 8 основных хуков жизненного цикла. Но не пугайтесь — вам не нужно использовать их все. Большинство реальных приложений обходятся тремя-четырьмя. Главное — знать, когда какой вызывается. Вот порядок вызова для компонента:
- constructor — вызывается самым первым, еще до того, как Angular свяжет свойства с шаблоном. Здесь инициализируют простые значения, но НЕ загружают данные.
- ngOnChanges — вызывается при каждом изменении входных свойств (Input). Даже при первой установке. Получает объект SimpleChanges с предыдущими и новыми значениями.
- ngOnInit — вызывается один раз после первого ngOnChanges. Это место для HTTP-запросов, инициализации сложной логики, подписок.
- ngDoCheck — вызывается при каждой проверке изменений. Тонкая настройка — используйте редко, только если нужно перехватить изменения, которые Angular не видит.
- ngAfterContentInit — вызывается один раз после проекции контента (ng-content). Полезно, если ваш компонент использует контент от родителя.
- ngAfterContentChecked — после каждой проверки проекции контента.
- ngAfterViewInit — вызывается один раз, когда дочерние компоненты и DOM полностью готовы. Здесь безопасно обращаться к ViewChild.
- ngAfterViewChecked — после каждой проверки представления. Используйте с осторожностью: может вызываться часто и влиять на производительность.
- ngOnDestroy — последний хук. Вызывается перед уничтожением компонента. Здесь отписываются от всех подписок, очищают таймеры, интервалы.
Как не запутаться в последовательности: реальный пример
Допустим, у вас есть компонент UserProfileComponent с входным свойством userId. Когда приходит новое значение userId, Angular сначала вызывает ngOnChanges. Вы видите: «Ага, userId изменился с 5 на 10». Потом вызывается ngOnInit — один раз. Затем ngDoCheck, ngAfterContentInit и так далее. Если вы попытаетесь загрузить данные в constructor, вы не узнаете, какой userId передан — он еще не установлен. Ошибка новичка №1.
Правильный подход: в ngOnInit делаете запрос к API, используя значение userId, которое уже доступно. А если userId может меняться в процессе жизни компонента, вы отслеживаете изменения в ngOnChanges и делаете новый запрос или используете switchMap из RxJS в ngOnChanges.
Тонкие моменты: когда стандартные хуки не срабатывают
Angular не вызывает повторно ngOnChanges, если свойство Input — это объект или массив, а вы изменяете его внутреннее состояние (например, добавляете элемент в массив). Angular смотрит на ссылку, а не на содержимое. Для таких случаев используйте ngDoCheck или immutable-подход (создавайте новый объект). Пример: если у вас есть список задач и вы добавляете задачу через push, ngOnChanges не сработает. Решение: сделать новый массив через spread-оператор — this.tasks = [...this.tasks, newTask].
Еще один нюанс: ngAfterViewInit не сработает, если у компонента нет шаблона (то есть он пустой). Это редкий случай, но бывает. И если вы используете директивы, которые модифицируют DOM, учтите: их жизненный цикл может отличаться от компонентного.
Практические советы: какие хуки использовать в реальных проектах
В 90% случаев вам понадобятся только:
- ngOnInit — для инициализации и загрузки данных.
- ngOnDestroy — для очистки подписок.
- ngOnChanges — если у компонента есть динамические Input-свойства.
Остальные хуки — это инструменты для специфических задач. ngDoCheck используйте только если уверены, что без него не обойтись (например, при работе с ChangeDetectionStrategy.OnPush и внешними библиотеками). ngAfterViewInit — для работы с ViewChild после рендера. И никогда не кладите тяжелую логику в constructor — это замедлит рендеринг.
Чем этот подход отличается от других фреймворков
В React нет хуков жизненного цикла компонента в классическом смысле — есть useEffect, который работает по-другому. В Vue есть watch и computed, но порядок вызова проще. Angular же строго регламентирует последовательность и дает тонкий контроль. Это как разница между автоматом и ручной коробкой передач: Angular требует больше внимания на старте, но дает больше возможностей для оптимизации.
Например, если вам нужно выполнить код после того, как все дочерние компоненты отрендерились, в Angular вы просто используете ngAfterViewInit. В React пришлось бы добавлять useLayoutEffect или setTimeout. Разница в пять строк кода, которая может спасти от багов.
Типичные ошибки и как их избежать
- Забыли отписаться в ngOnDestroy — классика. Если вы подписались на Observable, но не вызвали unsubscribe (или не использовали async pipe), компонент будет жить в памяти, даже если он уничтожен. Решение: в ngOnDestroy вызовите this.subscription.unsubscribe() или используйте оператор takeUntil с destroy-субъектом.
- Пытаетесь получить доступ к ViewChild в ngOnInit — не выйдет, потому что представление еще не инициализировано. Переносите в ngAfterViewInit.
- Вызываете HTTP-запрос в constructor — в constructor еще нет входных свойств, вы рискуете отправить запрос с undefined. Делайте это в ngOnInit.
Следуя этим правилам, вы сэкономите часы дебаггинга и сделаете приложение стабильнее.
Добавлено: 23.04.2026
