component lifecycle methods in Angular
Angular component lifecycle hooks are predefined methods Angular calls at specific points during a component's creation, updates, and removal from the DOM.
Theory
TL;DR
- Hooks fire in a fixed order:
ngOnChanges→ngOnInit→ ... →ngOnDestroy - Think of it like a construction checklist: foundation laid (
ngOnInit), wiring checked (ngAfterViewInit), final teardown (ngOnDestroy) - Use
ngOnInitfor data fetching andngOnDestroyfor cleanup (subscriptions, timers) ngOnChangesfires beforengOnInitand on every@Inputchange after thatngDoCheckruns on every change detection cycle — use it as a last resort
Quick example
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
@Component({
selector: 'app-user',
template: `<p>{{ user?.name }}</p>`
})
export class UserComponent implements OnInit, OnDestroy {
@Input() user: { name: string };
ngOnInit() {
// Runs once after @Input properties are bound
console.log('Component ready, user:', this.user);
}
ngOnDestroy() {
// Runs before the component leaves the DOM
console.log('Cleaning up');
}
}
// On create: "Component ready, user: {name: 'Alice'}"
// On destroy: "Cleaning up"In ngOnInit, @Input values are already set. In the constructor, they are not.
Hook execution order
Hooks fire in this sequence on component initialization:
| Hook | When triggered | Typical use |
|---|---|---|
ngOnChanges | Input property binds or changes | React to parent updates, log input diffs |
ngOnInit | After first ngOnChanges, bindings complete | Fetch data, init services |
ngDoCheck | Every change detection cycle | Custom equality checks |
ngAfterContentInit | Projected content (ng-content) ready | Query projected components |
ngAfterContentChecked | Content view checked | Post-content mutation logic |
ngAfterViewInit | Component + children views rendered | Access ViewChild, start animations |
ngAfterViewChecked | View checked | Fine-tune after full render |
ngOnDestroy | Before DOM removal | Cleanup subscriptions, timers |
After the first full pass, Angular cycles through ngDoCheck, ngAfterContentChecked, and ngAfterViewChecked on every subsequent change detection run.
Key difference: ngOnInit vs constructor
The constructor runs during dependency injection, before Angular binds any inputs. ngOnInit runs after the first change detection pass, when @Input values are already set. Putting a data fetch in the constructor means your service call runs with no context from the parent yet. That is the most common mistake you will see in Angular code reviews.
When to use each hook
- Data fetch or service call:
ngOnInit - React to parent
@Inputchanges:ngOnChangeswith aSimpleChangescheck - Access
ViewChildor start DOM animations:ngAfterViewInit - Query projected
ng-contentchildren:ngAfterContentInit - Cleanup (unsubscribe, cancel timers, close WebSocket):
ngOnDestroy - Custom detection when OnPush misses a mutation:
ngDoCheck(last resort only)
ngOnInit and ngOnDestroy cover 90% of real-world cases. The content and view hooks only matter when you work with ng-content or ViewChild.
How Angular runs hooks internally
Angular's Ivy renderer (default since Angular 9) creates a component instance, binds @Input via property setters (which triggers ngOnChanges), then runs change detection through Zone.js-patched async tasks. Hooks fire as the renderer traverses the component tree top-down. Post-render phases like ngAfterViewInit are queued as microtasks after the view is fully built. Ivy pre-generates lifecycle calls at compile time, which cuts runtime overhead compared to the older ViewEngine.
For a parent-child tree: parent ngOnInit fires first, then the child goes through its full cycle. Parent ngAfterViewInit fires only after all children have completed their view initialization.
Common mistakes
Fetching data in the constructor:
// Wrong - @Input values are not available yet
constructor(private svc: DataService) {
this.svc.getData(this.userId); // this.userId is undefined here
}
// Right
ngOnInit() {
this.svc.getData(this.userId); // @Input() userId is bound
}Accessing ViewChild before ngAfterViewInit:
// Wrong - view is not rendered yet
ngOnInit() {
console.log(this.myElement.nativeElement); // null
}
// Right
ngAfterViewInit() {
console.log(this.myElement.nativeElement); // works
}Skipping ngOnDestroy unsubscribe:
// Wrong - subscription lives forever, memory leak on every route change
ngOnInit() {
interval(1000).subscribe(v => this.data = v);
}
// Right - takeUntil pattern
private destroy$ = new Subject<void>();
ngOnInit() {
interval(1000).pipe(takeUntil(this.destroy$)).subscribe(v => this.data = v);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}ngOnChanges without checking which input changed:
// Runs full reload on every trivial input change
ngOnChanges() {
this.loadData();
}
// Check first
ngOnChanges(changes: SimpleChanges) {
if (changes['userId'] && !changes['userId'].firstChange) {
this.loadData();
}
}Real-world usage
- Angular Material dialogs:
ngAfterViewInitstarts animations once modal content is in the DOM - NGRX/NGXS stores:
ngOnDestroycleans up selectors and effects to prevent stale subscriptions - PrimeNG tables:
ngAfterViewCheckedhandles virtual scroll resize after each render cycle - Route-based components:
ngOnInitloads lazy data after route resolvers finish - OnPush components with mutable arrays:
ngDoCheck+markForCheck()when reference equality fails
Follow-up questions
Q: What is the difference between the constructor and ngOnInit?
A: Constructor runs during DI, before Angular binds any @Input properties. ngOnInit runs after the first change detection pass, so inputs are available. Use the constructor only for injecting dependencies.
Q: When does ngOnChanges fire relative to ngOnInit?
A: ngOnChanges fires first, before ngOnInit, whenever an @Input property is set or updated. If a component has no @Input, ngOnChanges never fires at all.
Q: Why is ngDoCheck a last resort?
A: It runs on every change detection cycle, even when nothing changed. That can mean hundreds of calls per second. Prefer OnPush with immutable data or Angular signals (v16+) before reaching for ngDoCheck.
Q: How does hook order work in a parent-child component tree?
A: Parent ngOnInit fires, then the child goes through its full lifecycle. Parent ngAfterViewInit fires only after all children have completed their own view initialization.
Q: OnPush component with a mutable @Input array - why does the UI stop updating and how do you fix it?
A: OnPush skips change detection if the input reference has not changed. Mutating an array with .push() or .splice() keeps the same reference, so Angular skips the component entirely. Fix with ngDoCheck + cdRef.markForCheck(), or better: replace the array with a new reference on every update.
Examples
Basic: tracking input changes with ngOnChanges
@Component({
selector: 'app-counter',
template: '<button (click)="increment()">Count: {{ count }}</button>'
})
export class CounterComponent implements OnChanges {
@Input() max = 10;
count = 0;
ngOnChanges(changes: SimpleChanges) {
if (changes['max']) {
// Only react to the specific input that changed
console.log('Max changed to', changes['max'].currentValue);
if (this.count > changes['max'].currentValue) {
this.count = 0; // reset if over new limit
}
}
}
increment() { this.count++; }
}
// Parent changes @Input max from 10 to 5 → "Max changed to 5"
// If count was 7, it resets to 0SimpleChanges gives you both currentValue and previousValue. Without it you are reacting blindly to every change detection cycle.
Intermediate: data loading with cleanup
This pattern covers the most common production scenario: fetch on init, cancel on destroy. It comes directly from Angular's Tour of Heroes architecture.
@Component({
template: `
<div *ngIf="hero$ | async as hero">
{{ hero.name }}
</div>
`
})
export class HeroDetailComponent implements OnInit, OnDestroy {
hero$: Observable<Hero>;
private destroy$ = new Subject<void>();
constructor(
private heroService: HeroService,
private route: ActivatedRoute
) {}
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
this.hero$ = this.heroService.getHero(id).pipe(
takeUntil(this.destroy$)
);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete(); // prevents memory leak on route change
}
}
// Hero loads on component init
// Subscription cancels cleanly when user navigates awayAdvanced: ngDoCheck with OnPush for mutable array detection
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<ul><li *ngFor="let item of items">{{ item }}</li></ul>`
})
export class ItemListComponent implements DoCheck {
@Input() items: string[] = [];
private prevLength = 0;
constructor(private cdRef: ChangeDetectorRef) {}
ngDoCheck() {
// OnPush misses array.push() because the reference stays the same
if (this.items.length !== this.prevLength) {
this.prevLength = this.items.length;
this.cdRef.markForCheck(); // force re-render for this subtree
}
}
}
// Without ngDoCheck: UI freezes after array.push()
// With it: rerenders on length change, but ngDoCheck runs every cycle (perf cost)
// Better alternative: replace the array with a new reference on every updateIn production I prefer the immutable update approach over ngDoCheck. The markForCheck path works, but every extra comparison you add to ngDoCheck eventually becomes a performance conversation during code review.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.