Skip to main content

Lifecycle Hooks

Hook Order

  1. ngOnChanges - Input properties change
  2. ngOnInit - Component initialized
  3. ngDoCheck - Change detection
  4. ngAfterContentInit - Content projected
  5. ngAfterContentChecked - Content checked
  6. ngAfterViewInit - View initialized
  7. ngAfterViewChecked - View checked
  8. ngOnDestroy - 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 @ViewChild elements
  • 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 @ContentChild elements
  • 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 ngOnInit for initialization (not constructor)
  • Always implement ngOnDestroy for cleanup
  • Avoid heavy logic in ngDoCheck
  • Use takeUntil() pattern for unsubscribing
  • ngAfterViewInit for DOM access
  • ngOnChanges for input-dependent logic