Angular — Жизненный цикл компонента
Жизненный цикл начинается когда Angular создаёт экземпляр компонента и рендерит представление, и заканчивается при уничтожении компонента и удалении его из DOM.
Порядок вызовов хуков
constructor()
↓
ngOnChanges() ← вызывается до ngOnInit и при каждом изменении @Input
↓
ngOnInit() ← один раз после первого ngOnChanges
↓
ngDoCheck() ← каждый цикл Change Detection
↓
ngAfterContentInit() ← один раз после проекции ng-content
↓
ngAfterContentChecked() ← после каждой проверки контента
↓
ngAfterViewInit() ← один раз после инициализации View
↓
ngAfterViewChecked() ← после каждой проверки View
↓
ngOnDestroy() ← перед уничтожением компонента
Хуки подробно
constructor()
Не является хуком Angular — это конструктор класса TypeScript.
- Вызывается первым при создании экземпляра
@Input()-свойства ещё не заполнены- Использовать только для DI (внедрения зависимостей)
- ❌ Нельзя делать HTTP-запросы, работать с DOM
constructor(private authService: AuthService) {}ngOnChanges(changes: SimpleChanges)
- Вызывается до
ngOnInitи при каждом изменении@Input() - Получает объект
SimpleChangesс предыдущими и текущими значениями - ❗ Вызывается только если у компонента есть хотя бы один
@Input()
ngOnChanges(changes: SimpleChanges): void {
if (changes['user']) {
const prev = changes['user'].previousValue;
const curr = changes['user'].currentValue;
console.log(`User changed: ${prev?.name} → ${curr?.name}`);
}
}ngOnInit()
- Вызывается один раз после первого
ngOnChanges @Input()-свойства уже заполнены- Основное место для инициализации: HTTP-запросы, подписки, начальная логика
ngOnInit(): void {
this.userService.getUser(this.userId).subscribe(user => {
this.user = user;
});
}ngDoCheck()
- Вызывается при каждом запуске Change Detection (очень часто!)
- Позволяет реагировать на изменения, которые Angular не отловил сам
- ⚠️ Использовать осторожно — может сильно снижать производительность
ngDoCheck(): void {
if (this.user.name !== this.prevName) {
this.prevName = this.user.name;
// реакция на изменение
}
}ngAfterContentInit()
- Вызывается один раз после того, как Angular спроецировал внешний контент через
<ng-content> - Здесь можно безопасно работать с
@ContentChild/@ContentChildren
@ContentChild(HeaderComponent) header!: HeaderComponent;
ngAfterContentInit(): void {
console.log('Header:', this.header);
}ngAfterContentChecked()
- Вызывается после каждой проверки спроецированного контента
- Срабатывает сразу после
ngAfterContentInitи затем после каждогоngDoCheck - ⚠️ Вызывается очень часто
ngAfterViewInit()
- Вызывается один раз после инициализации представления компонента и всех дочерних компонентов
- Здесь можно безопасно работать с
@ViewChild/@ViewChildren - ❗ Нельзя изменять данные, которые влияют на шаблон (вызовет
ExpressionChangedAfterItHasBeenCheckedError)
@ViewChild('canvas') canvas!: ElementRef<HTMLCanvasElement>;
ngAfterViewInit(): void {
const ctx = this.canvas.nativeElement.getContext('2d');
// работа с Canvas
}ngAfterViewChecked()
- Вызывается после каждой проверки представления компонента и дочерних компонентов
- Срабатывает сразу после
ngAfterViewInitи затем после каждого цикла CD - ⚠️ Вызывается очень часто — минимум логики
ngOnDestroy()
- Вызывается один раз непосредственно перед уничтожением компонента
- Обязательное место для очистки ресурсов во избежание утечек памяти
private destroy$ = new Subject<void>();
ngOnInit(): void {
this.data$.pipe(takeUntil(this.destroy$)).subscribe(...);
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
// Отписки, clearInterval, removeEventListener
}💡 Современная альтернатива —
takeUntilDestroyed()из@angular/core/rxjs-interop(Angular 16+), который не требует ручногоngOnDestroy.
Сводная таблица
| Хук | Количество вызовов | Когда | Типичное использование |
|---|---|---|---|
constructor | 1 | При создании класса | Внедрение зависимостей |
ngOnChanges | N | При изменении @Input() | Реакция на изменение входных данных |
ngOnInit | 1 | После первого ngOnChanges | HTTP-запросы, инициализация |
ngDoCheck | N | Каждый цикл CD | Ручная проверка изменений |
ngAfterContentInit | 1 | После проекции контента | Работа с @ContentChild |
ngAfterContentChecked | N | После проверки контента | Редко используется |
ngAfterViewInit | 1 | После инициализации View | Работа с @ViewChild, DOM |
ngAfterViewChecked | N | После проверки View | Редко используется |
ngOnDestroy | 1 | Перед уничтожением | Отписки, очистка ресурсов |
Интерфейсы
Каждый хук соответствует интерфейсу — рекомендуется явно их реализовывать:
import {
OnChanges, OnInit, DoCheck,
AfterContentInit, AfterContentChecked,
AfterViewInit, AfterViewChecked,
OnDestroy
} from '@angular/core';
@Component({ selector: 'app-example', template: '' })
export class ExampleComponent implements OnInit, OnDestroy {
ngOnInit(): void { }
ngOnDestroy(): void { }
}Полный пример компонента
@Component({
selector: 'app-user',
template: `<p>{{ user.name }}</p>`
})
export class UserComponent implements OnChanges, OnInit, OnDestroy {
@Input() userId!: number;
user!: User;
private destroy$ = new Subject<void>();
constructor(private userService: UserService) {}
ngOnChanges(changes: SimpleChanges): void {
if (changes['userId'] && !changes['userId'].firstChange) {
this.loadUser(); // перезагрузить при смене userId
}
}
ngOnInit(): void {
this.loadUser();
}
private loadUser(): void {
this.userService.getUser(this.userId)
.pipe(takeUntil(this.destroy$))
.subscribe(u => this.user = u);
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}