Lifecycle Hooks
Hook Order
ngOnChanges- Input properties changengOnInit- Component initializedngDoCheck- Change detectionngAfterContentInit- Content projectedngAfterContentChecked- Content checkedngAfterViewInit- View initializedngAfterViewChecked- View checkedngOnDestroy- Before component destroyed
ngOnInit
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-user'
})
export class UserComponent implements OnInit {
ngOnInit() {
// Initialize component
// Load data, setup subscriptions
this.loadUsers();
}
loadUsers() {
this.userService.getUsers().subscribe(users => {
this.users = users;
});
}
}
Use for:
- Initial data loading
- Setup subscriptions
- Component initialization logic
ngOnChanges
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-user-detail'
})
export class UserDetailComponent implements OnChanges {
@Input() userId!: number;
ngOnChanges(changes: SimpleChanges) {
if (changes['userId']) {
const current = changes['userId'].currentValue;
const previous = changes['userId'].previousValue;
const firstChange = changes['userId'].firstChange;
console.log(`userId changed from ${previous} to ${current}`);
if (!firstChange) {
this.loadUser(current);
}
}
}
}
Use for:
- React to
@Input()changes - Update derived data when inputs change
ngOnDestroy
import { Component, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-user'
})
export class UserComponent implements OnDestroy {
private destroy$ = new Subject<void>();
ngOnInit() {
this.userService.getUsers()
.pipe(takeUntil(this.destroy$))
.subscribe(users => this.users = users);
// Or manual subscription
this.subscription = this.userService.getUsers().subscribe();
}
ngOnDestroy() {
// Cleanup
this.destroy$.next();
this.destroy$.complete();
// Or
this.subscription?.unsubscribe();
}
}
Use for:
- Unsubscribe from observables
- Clear timers/intervals
- Remove event listeners
- Cleanup resources
ngAfterViewInit
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-input-focus',
template: `<input #inputField>`
})
export class InputFocusComponent implements AfterViewInit {
@ViewChild('inputField') inputField!: ElementRef;
ngAfterViewInit() {
// View is ready, can access DOM
this.inputField.nativeElement.focus();
}
}
Use for:
- Access
@ViewChildelements - DOM manipulation
- Initialize third-party libraries
ngAfterContentInit
import { Component, ContentChild, AfterContentInit } from '@angular/core';
@Component({
selector: 'app-wrapper',
template: `<ng-content></ng-content>`
})
export class WrapperComponent implements AfterContentInit {
@ContentChild(ChildComponent) child!: ChildComponent;
ngAfterContentInit() {
// Projected content is ready
console.log('Child component:', this.child);
}
}
Use for:
- Access
@ContentChildelements - Work with projected content
ngDoCheck
import { Component, DoCheck } from '@angular/core';
@Component({
selector: 'app-custom-check'
})
export class CustomCheckComponent implements DoCheck {
lastValue: any;
ngDoCheck() {
// Called on every change detection
// Use sparingly - performance impact!
if (this.value !== this.lastValue) {
console.log('Value changed manually detected');
this.lastValue = this.value;
}
}
}
Use for:
- Custom change detection
- Detect changes Angular doesn't catch
- Caution: Called very frequently
Complete Example
@Component({
selector: 'app-lifecycle'
})
export class LifecycleComponent
implements OnInit, OnChanges, AfterViewInit, OnDestroy {
@Input() data: any;
@ViewChild('element') element!: ElementRef;
private destroy$ = new Subject<void>();
constructor() {
console.log('1. Constructor');
}
ngOnChanges(changes: SimpleChanges) {
console.log('2. ngOnChanges', changes);
}
ngOnInit() {
console.log('3. ngOnInit');
this.loadData();
}
ngAfterViewInit() {
console.log('4. ngAfterViewInit');
this.element.nativeElement.focus();
}
ngOnDestroy() {
console.log('5. ngOnDestroy');
this.destroy$.next();
this.destroy$.complete();
}
private loadData() {
this.service.getData()
.pipe(takeUntil(this.destroy$))
.subscribe(data => this.processData(data));
}
}
Best Practices
- Use
ngOnInitfor initialization (not constructor) - Always implement
ngOnDestroyfor cleanup - Avoid heavy logic in
ngDoCheck - Use
takeUntil()pattern for unsubscribing ngAfterViewInitfor DOM accessngOnChangesfor input-dependent logic