Overview
Lifecycle hooks provide visibility into key moments in a component or directive’s lifecycle, from creation to destruction. Angular calls these hook methods in a specific sequence.
Lifecycle Sequence
Constructor - Class instantiation (not a hook)
OnChanges - Input property changes
OnInit - Initialization after first change detection
DoCheck - Custom change detection
AfterContentInit - Content projection initialized (once)
AfterContentChecked - Content projection checked
AfterViewInit - View initialized (once)
AfterViewChecked - View checked
OnDestroy - Cleanup before destruction
OnChanges
Interface
interface OnChanges {
ngOnChanges ( changes : SimpleChanges ) : void ;
}
Description
Called when any data-bound input property changes. Receives a SimpleChanges object containing current and previous values.
Parameters
Object containing changed properties with their current and previous values. {
propertyName : {
currentValue : any ,
previousValue : any ,
firstChange : boolean
}
}
Example
user-profile.component.ts
import { Component , Input , OnChanges , SimpleChanges } from '@angular/core' ;
@ Component ({
selector: 'app-user-profile' ,
standalone: true ,
template: `
<div>
<h3>{{userName}}</h3>
<p>Changes: {{changeCount}}</p>
</div>
`
})
export class UserProfileComponent implements OnChanges {
@ Input () userName : string = '' ;
@ Input () userId : number = 0 ;
changeCount = 0 ;
ngOnChanges ( changes : SimpleChanges ) {
this . changeCount ++ ;
if ( changes [ 'userName' ]) {
console . log ( 'userName changed:' , {
previous: changes [ 'userName' ]. previousValue ,
current: changes [ 'userName' ]. currentValue ,
first: changes [ 'userName' ]. firstChange
});
}
if ( changes [ 'userId' ] && ! changes [ 'userId' ]. firstChange ) {
// Reload user data when ID changes (but not on first load)
this . loadUserData ( changes [ 'userId' ]. currentValue );
}
}
private loadUserData ( id : number ) {
console . log ( 'Loading data for user:' , id );
}
}
ngOnChanges is called before ngOnInit and whenever input properties change. It’s not called for internal state changes.
OnInit
Interface
interface OnInit {
ngOnInit () : void ;
}
Description
Called once after the first ngOnChanges. Used for component initialization logic.
Example
import { Component , OnInit } from '@angular/core' ;
import { DataService } from './data.service' ;
@ Component ({
selector: 'app-data-list' ,
standalone: true ,
template: `
<ul>
<li *ngFor="let item of items">{{item}}</li>
</ul>
`
})
export class DataListComponent implements OnInit {
items : string [] = [];
constructor ( private dataService : DataService ) {
// Constructor should be lightweight
console . log ( 'Constructor called' );
}
ngOnInit () {
// Initialization logic goes here
console . log ( 'ngOnInit called' );
this . loadData ();
}
private loadData () {
this . dataService . getData (). subscribe ( data => {
this . items = data ;
});
}
}
Use ngOnInit for initialization logic that requires input properties or depends on services. Keep constructors lightweight.
DoCheck
Interface
interface DoCheck {
ngDoCheck () : void ;
}
Description
Called during every change detection cycle. Use for custom change detection logic.
Example
custom-check.component.ts
import { Component , Input , DoCheck } from '@angular/core' ;
@ Component ({
selector: 'app-custom-check' ,
standalone: true ,
template: `
<div>
<p>Items: {{items.length}}</p>
<p>Last check: {{lastCheck}}</p>
</div>
`
})
export class CustomCheckComponent implements DoCheck {
@ Input () items : string [] = [];
lastCheck : Date = new Date ();
private previousLength = 0 ;
ngDoCheck () {
this . lastCheck = new Date ();
// Custom check for array length changes
if ( this . items . length !== this . previousLength ) {
console . log ( 'Array length changed:' , this . previousLength , '->' , this . items . length );
this . previousLength = this . items . length ;
}
}
}
ngDoCheck is called very frequently. Keep its logic lightweight to avoid performance issues.
AfterContentInit
Interface
interface AfterContentInit {
ngAfterContentInit () : void ;
}
Description
Called once after Angular projects external content into the component’s view (content children).
Example
tab-container.component.ts
import { Component , ContentChildren , QueryList , AfterContentInit } from '@angular/core' ;
import { TabComponent } from './tab.component' ;
@ Component ({
selector: 'app-tab-container' ,
standalone: true ,
template: `
<div class="tab-container">
<ng-content></ng-content>
</div>
`
})
export class TabContainerComponent implements AfterContentInit {
@ ContentChildren ( TabComponent ) tabs !: QueryList < TabComponent >;
ngAfterContentInit () {
console . log ( 'Content initialized' );
console . log ( `Found ${ this . tabs . length } tabs` );
// Activate the first tab
if ( this . tabs . length > 0 ) {
this . tabs . first . active = true ;
}
}
}
AfterContentChecked
Interface
interface AfterContentChecked {
ngAfterContentChecked () : void ;
}
Description
Called after Angular checks the content projected into the component.
Example
content-wrapper.component.ts
import { Component , ContentChild , AfterContentChecked , ElementRef } from '@angular/core' ;
@ Component ({
selector: 'app-content-wrapper' ,
standalone: true ,
template: `
<div class="wrapper">
<ng-content></ng-content>
<p>Content height: {{contentHeight}}px</p>
</div>
`
})
export class ContentWrapperComponent implements AfterContentChecked {
@ ContentChild ( 'content' ) content ?: ElementRef ;
contentHeight = 0 ;
ngAfterContentChecked () {
if ( this . content ) {
this . contentHeight = this . content . nativeElement . offsetHeight ;
}
}
}
AfterViewInit
Interface
interface AfterViewInit {
ngAfterViewInit () : void ;
}
Description
Called once after Angular initializes the component’s views and child views.
Example
import { Component , ViewChild , ElementRef , AfterViewInit } from '@angular/core' ;
@ Component ({
selector: 'app-chart' ,
standalone: true ,
template: `
<canvas #chartCanvas></canvas>
`
})
export class ChartComponent implements AfterViewInit {
@ ViewChild ( 'chartCanvas' ) canvas !: ElementRef < HTMLCanvasElement >;
ngAfterViewInit () {
console . log ( 'View initialized' );
this . initializeChart ();
}
private initializeChart () {
const ctx = this . canvas . nativeElement . getContext ( '2d' );
if ( ctx ) {
// Initialize chart library
console . log ( 'Canvas ready:' , this . canvas . nativeElement . width );
}
}
}
View children are guaranteed to be initialized in ngAfterViewInit. Don’t try to access them in ngOnInit.
AfterViewChecked
Interface
interface AfterViewChecked {
ngAfterViewChecked () : void ;
}
Description
Called after Angular checks the component’s views and child views.
Example
scroll-monitor.component.ts
import { Component , ViewChild , ElementRef , AfterViewChecked } from '@angular/core' ;
@ Component ({
selector: 'app-scroll-monitor' ,
standalone: true ,
template: `
<div #scrollContainer class="container">
<div class="content">{{content}}</div>
</div>
<p>Scroll position: {{scrollPosition}}</p>
`
})
export class ScrollMonitorComponent implements AfterViewChecked {
@ ViewChild ( 'scrollContainer' ) container !: ElementRef ;
scrollPosition = 0 ;
content = 'Some content...' ;
ngAfterViewChecked () {
if ( this . container ) {
this . scrollPosition = this . container . nativeElement . scrollTop ;
}
}
}
Be cautious with ngAfterViewChecked - it’s called very frequently. Avoid expensive operations or state changes that trigger additional change detection.
OnDestroy
Interface
interface OnDestroy {
ngOnDestroy () : void ;
}
Description
Called just before Angular destroys the component or directive. Used for cleanup logic.
Example
import { Component , OnInit , OnDestroy } from '@angular/core' ;
import { interval , Subscription } from 'rxjs' ;
@ Component ({
selector: 'app-timer' ,
standalone: true ,
template: `
<div>
<p>Elapsed: {{elapsed}} seconds</p>
</div>
`
})
export class TimerComponent implements OnInit , OnDestroy {
elapsed = 0 ;
private subscription ?: Subscription ;
ngOnInit () {
console . log ( 'Timer started' );
this . subscription = interval ( 1000 ). subscribe (() => {
this . elapsed ++ ;
});
}
ngOnDestroy () {
console . log ( 'Timer stopped' );
// Clean up subscription to prevent memory leaks
if ( this . subscription ) {
this . subscription . unsubscribe ();
}
}
}
Always clean up in ngOnDestroy:
Unsubscribe from Observables
Clear intervals and timeouts
Detach event listeners
Cancel pending HTTP requests
Complete Lifecycle Example
full-lifecycle.component.ts
import {
Component ,
Input ,
OnChanges ,
OnInit ,
DoCheck ,
AfterContentInit ,
AfterContentChecked ,
AfterViewInit ,
AfterViewChecked ,
OnDestroy ,
SimpleChanges
} from '@angular/core' ;
@ Component ({
selector: 'app-full-lifecycle' ,
standalone: true ,
template: `
<div>
<h3>{{title}}</h3>
<ng-content></ng-content>
</div>
`
})
export class FullLifecycleComponent implements
OnChanges ,
OnInit ,
DoCheck ,
AfterContentInit ,
AfterContentChecked ,
AfterViewInit ,
AfterViewChecked ,
OnDestroy {
@ Input () title = 'Component' ;
constructor () {
console . log ( '1. Constructor' );
}
ngOnChanges ( changes : SimpleChanges ) {
console . log ( '2. ngOnChanges' , changes );
}
ngOnInit () {
console . log ( '3. ngOnInit' );
}
ngDoCheck () {
console . log ( '4. ngDoCheck' );
}
ngAfterContentInit () {
console . log ( '5. ngAfterContentInit' );
}
ngAfterContentChecked () {
console . log ( '6. ngAfterContentChecked' );
}
ngAfterViewInit () {
console . log ( '7. ngAfterViewInit' );
}
ngAfterViewChecked () {
console . log ( '8. ngAfterViewChecked' );
}
ngOnDestroy () {
console . log ( '9. ngOnDestroy' );
}
}
Hook Call Frequency
Hook Frequency When to Use ngOnChangesOn input changes React to input changes ngOnInitOnce Initialization logic ngDoCheckEvery CD cycle Custom change detection ngAfterContentInitOnce Access content children ngAfterContentCheckedEvery CD cycle Monitor content changes ngAfterViewInitOnce Access view children, DOM manipulation ngAfterViewCheckedEvery CD cycle Monitor view changes ngOnDestroyOnce Cleanup
Best Practices
Use ngOnInit for most initialization logic
Keep ngDoCheck, ngAfterContentChecked, and ngAfterViewChecked lightweight
Always implement ngOnDestroy for components with subscriptions
Use ngAfterViewInit to access child components and DOM elements
Prefer ngOnChanges over getters/setters for input change detection
Don’t modify component state in ngAfterViewChecked (causes ExpressionChangedAfterItHasBeenCheckedError)
Avoid heavy computations in frequently called hooks
Don’t forget to unsubscribe from Observables in ngOnDestroy
Be careful with async operations in lifecycle hooks
Signal-Based Alternative
With Angular signals, many lifecycle hooks can be replaced:
import { Component , input , effect , OnDestroy } from '@angular/core' ;
@ Component ({
selector: 'app-signal-component' ,
standalone: true ,
template: `<p>{{name()}}: {{count()}}</p>`
})
export class SignalComponent implements OnDestroy {
name = input ( 'Item' );
count = input ( 0 );
// Automatically runs when signals change
private logEffect = effect (() => {
console . log ( ` ${ this . name () } count is ${ this . count () } ` );
});
ngOnDestroy () {
// Effects are automatically cleaned up
}
}
See Also
Lifecycle Guide Component lifecycle guide
Change Detection How change detection works
Queries Query child components
Signals Modern reactive patterns