1. ngOnInit: когда компонент готов к работе
Любой компонент Angular проходит через ряд этапов жизни: он появляется на свет (создается), получает входные данные, появляется на экране, обновляется при изменении входных данных, а потом уходит в "лучший мир" (удаляется из DOM). На каждом из этих этапов фреймворк дает вам возможность "вмешаться" — выполнить свой код в нужный момент. Такие специальные методы называются хуками жизненного цикла (lifecycle hooks).
Если проводить аналогию с театром: у актера есть репетиция, выход на сцену, аплодисменты, и, наконец, уход за кулисы. Angular — строгий режиссер, который всегда зовет вас на сцену вовремя, если вы объявили нужный "хук".
В этой лекции мы рассмотрим три самых важных хука, без которых не обходится почти ни один реальный компонент:
- ngOnInit — инициализация компонента
- ngOnChanges — реагирование на изменения входных данных
- ngOnDestroy — уборка за собой перед уничтожением
Что это такое?
ngOnInit — это метод, который автоматически вызывается Angular сразу после того, как компонент создан и все его входные свойства (@Input) и зависимости инициализированы. Это отличное место для:
- Первичной загрузки данных (например, запрос к серверу)
- Инициализации переменных, которые зависят от входных данных
- Запуска таймеров, подписки на события и т.д.
Чтобы использовать этот хук, ваш компонент должен реализовать интерфейс OnInit (это не обязательно, но очень рекомендуется для читаемости и автокомплита в редакторе).
Пример: Использование ngOnInit
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-greeting',
template: `<p>{{ greeting }}</p>`
})
export class GreetingComponent implements OnInit {
greeting = '';
ngOnInit() {
// Здесь компонент уже полностью создан и все @Input() доступны
this.greeting = 'Добро пожаловать в Angular!';
// Можно делать запросы к серверу, запускать подписки и т.д.
}
}
Заметка:
Конструктор компонента вызывается раньше, но в нем еще не будут доступны значения @Input(). Вся логика, которая зависит от входных данных, должна быть в ngOnInit или позже.
Когда срабатывает ngOnInit?
- После инициализации всех данных, но до первого отображения компонента на экране.
- Вызывается только один раз за жизнь компонента.
// Шутка: Если вы перенесете загрузку данных в конструктор, Angular вас не накажет, но коллеги — могут. Не стоит так делать!
2. ngOnChanges: реагируем на изменения входных данных
Что это такое?
ngOnChanges — это метод, который вызывается каждый раз, когда Angular обнаруживает изменение одного или нескольких входных свойств (@Input()) компонента. Это особенно важно для вложенных компонентов, которые должны реагировать на изменения данных, пришедших от родителя.
Метод получает один параметр — объект типа SimpleChanges, в котором содержится информация о новых и старых значениях всех измененных входных свойств.
Пример: Использование ngOnChanges
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-score',
template: `<p>Текущий счет: {{ score }}</p>`
})
export class ScoreComponent implements OnChanges {
@Input() score = 0;
ngOnChanges(changes: SimpleChanges) {
if (changes['score']) {
const prev = changes['score'].previousValue;
const curr = changes['score'].currentValue;
console.log(`Счет изменился с ${prev} на ${curr}`);
}
}
}
Как это работает?
- Каждый раз, когда родительский компонент меняет значение score, Angular вызывает ngOnChanges в дочернем компоненте.
- В объекте changes можно посмотреть, что именно изменилось, и как (старое и новое значение).
Когда используется ngOnChanges?
- Когда компонент должен реагировать на изменения входных данных (например, подгружать новые данные, сбрасывать состояние, валидировать значения и т.д.).
- Когда нужно выполнять логику только при определенных изменениях.
Важно: ngOnChanges вызывается даже раньше, чем ngOnInit — сразу после инициализации входных свойств.
Пример из жизни
Допустим, у вас есть компонент <app-user-card [userId]="selectedUserId"></app-user-card>. Каждый раз, когда пользователь выбирает другого пользователя, Angular вызовет ngOnChanges в UserCardComponent, и вы сможете загрузить новую информацию о пользователе.
3. ngOnDestroy: уборка за собой
Что это такое?
ngOnDestroy — это метод, который Angular вызывает непосредственно перед тем, как удалить компонент из DOM и освободить занимаемые им ресурсы. Здесь вы должны:
- Отписываться от всех подписок (Observable, EventEmitter и т.д.)
- Очищать таймеры и интервалы (setTimeout, setInterval)
- Удалять слушателей событий, если добавляли их вручную
Если этого не делать, то в приложении могут возникнуть "утечки памяти" — неиспользуемые объекты останутся в памяти браузера, и приложение начнет тормозить (или даже падать).
Пример: Использование ngOnDestroy
import { Component, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'app-timer',
template: `<p>Секундомер: {{ seconds }} сек.</p>`
})
export class TimerComponent implements OnInit, OnDestroy {
seconds = 0;
private intervalId: any;
ngOnInit() {
this.intervalId = setInterval(() => {
this.seconds++;
}, 1000);
}
ngOnDestroy() {
// Очищаем таймер, чтобы не было утечки памяти
clearInterval(this.intervalId);
console.log('Таймер уничтожен');
}
}
Не забудьте:
Если вы подписываетесь на Observable (например, через RxJS), обязательно отписывайтесь в ngOnDestroy:
export class TimerComponent implements OnInit, OnDestroy {
private subscription: Subscription;
ngOnInit() {
this.subscription = someObservable.subscribe(data => {
// обработка данных
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Когда вызывается ngOnDestroy?
- Каждый раз, когда Angular удаляет компонент из DOM (например, при смене маршрута, условном отображении через *ngIf и т.д.)
- Вызывается только один раз за жизнь компонента.
4. Практика: Реализация простого приложения с хуками
Давайте разовьем наше учебное приложение. Представим, что у нас есть компонент, который отображает информацию о выбранном пользователе и обновляет счетчик времени, сколько эта карточка видна на экране.
Родительский компонент (упрощенно):
@Component({
selector: 'app-root',
template: `
<button (click)="selectUser(1)">Пользователь 1</button>
<button (click)="selectUser(2)">Пользователь 2</button>
<app-user-card [userId]="selectedUserId"></app-user-card>
`
})
export class AppComponent {
selectedUserId = 1;
selectUser(id: number) {
this.selectedUserId = id;
}
}
Дочерний компонент с хуками:
import { Component, Input, OnInit, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-user-card',
template: `
<div>
<p>Пользователь: {{ userId }}</p>
<p>Время на экране: {{ seconds }} сек.</p>
</div>
`
})
export class UserCardComponent implements OnInit, OnChanges, OnDestroy {
@Input() userId!: number;
seconds = 0;
private intervalId: any;
ngOnInit() {
this.seconds = 0;
this.intervalId = setInterval(() => this.seconds++, 1000);
console.log('UserCardComponent инициализирован');
}
ngOnChanges(changes: SimpleChanges) {
if (changes['userId'] && !changes['userId'].firstChange) {
console.log('userId изменился:', changes['userId'].previousValue, '→', changes['userId'].currentValue);
this.seconds = 0; // сбрасываем таймер при смене пользователя
}
}
ngOnDestroy() {
clearInterval(this.intervalId);
console.log('UserCardComponent уничтожен');
}
}
Что происходит:
- При первом появлении карточки вызывается ngOnInit, запускается таймер.
- Если родитель меняет userId, срабатывает ngOnChanges, таймер сбрасывается.
- Если компонент удаляется (например, через *ngIf), вызывается ngOnDestroy, таймер очищается.
5. Типичные ошибки при работе с хуками жизненного цикла
Ошибка №1: Логика, зависящая от входных данных, находится в конструкторе.
В конструкторе значения @Input() еще не определены. Не пытайтесь обращаться к ним — используйте для этого ngOnInit или ngOnChanges.
Ошибка №2: Забыли отписаться от подписок или не очистили таймеры в ngOnDestroy.
Это приводит к утечкам памяти и "призрачным" событиям, которые продолжают срабатывать даже после удаления компонента.
Ошибка №3: Не реализован интерфейс (OnInit, OnChanges, OnDestroy).
Angular все равно вызовет методы с правильными именами, но без интерфейса вы лишаетесь автокомплита и проверки типов. Лучше всегда явно реализовывать интерфейс.
Ошибка №4: Не обработан случай первого изменения в ngOnChanges.
В объекте SimpleChanges есть свойство firstChange, которое бывает полезно, чтобы отличать первое присваивание от последующих изменений.
Ошибка №5: Сброс состояния в неправильном хуке.
Если вы сбрасываете состояние в ngOnInit, а оно должно сбрасываться при каждом изменении входных данных — используйте ngOnChanges.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ