Angular Lifecycle Hooks
Learn Angular Lifecycle Hooks, including constructor vs ngOnInit, ngOnChanges, ngDoCheck, content and view lifecycle hooks, ngOnDestroy, lifecycle order, ViewChild timing, cleanup with DestroyRef and takeUntilDestroyed, Signals, performance mistakes, and interview questions.
Learning Objectives
By the end of this lesson, you will understand:
- What the Angular component lifecycle is and why Angular provides Lifecycle Hooks.
- The difference between the
constructorandngOnInit. - How
ngOnChanges,ngOnInit,ngDoCheck, content hooks, view hooks, andngOnDestroywork. - How to safely access
ViewChildand projected content. - How to clean up subscriptions, timers, event listeners, WebSockets, and third-party resources.
- How modern Angular uses
DestroyRef,takeUntilDestroyed(), Signals, andAsyncPipe.
Introduction
Every Angular component has a lifecycle. Angular creates it, initializes it, renders it, checks it for changes, updates it, and eventually destroys it.
Created│▼Initialized│▼Rendered│▼Checked for Changes│▼Updated│▼Destroyed
Lifecycle Hooks give developers well-defined points to load data, react to inputs, access the rendered view, initialize third-party libraries, and clean up resources.
A Real-World Story
Imagine a stock market dashboard. The component receives a stockSymbol, loads stock information, reacts when the symbol changes, initializes a chart after the view exists, receives live market updates, and stops those updates when the user leaves.
Component Created│▼Receive Input│▼Initialize Component│▼Load Initial Data│▼Render View│▼Initialize Chart│▼Receive Live Updates│▼Component Destroyed│▼Stop Subscriptions
What Are Angular Lifecycle Hooks?
Lifecycle Hooks are methods Angular calls at specific stages of a component or directive lifecycle.
ngOnChangesngOnInitngDoCheckngAfterContentInitngAfterContentCheckedngAfterViewInitngAfterViewCheckedngOnDestroy
Most components should use only the hooks they actually need.
Complete Lifecycle Overview
ngOnChanges: react to input updates.ngOnInit: initial setup.ngDoCheck: custom change detection logic.ngAfterContentInit: projected content initialized.ngAfterContentChecked: projected content checked.ngAfterViewInit: component view and child views initialized.ngAfterViewChecked: component view and child views checked.ngOnDestroy: cleanup resources.
The Constructor
The constructor is a TypeScript/JavaScript class feature. Angular commonly uses it for Dependency Injection, but it is not an Angular Lifecycle Hook.
export class ProductComponent {constructor(private productService: ProductService) {}}
Constructor vs ngOnInit
Use the constructor primarily for Dependency Injection and basic class setup. Use ngOnInit for component initialization after Angular has initialized input values for the first lifecycle pass.
Constructor│▼Create Class Instance│▼Angular Sets Inputs│▼ngOnChanges│▼ngOnInit
Why Avoid API Calls in the Constructor?
API calls in the constructor mix object construction with component initialization behavior. Keep dependency setup in the constructor and initialization behavior in ngOnInit.
constructor(private productService: ProductService) {}ngOnInit(): void {this.loadProducts();}
ngOnChanges
ngOnChanges executes when Angular sets or changes bound input properties.
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';@Component({selector: 'app-product-details',templateUrl: './product-details.component.html'})export class ProductDetailsComponent implements OnChanges {@Input()productId!: number;ngOnChanges(changes: SimpleChanges): void {console.log(changes);}}
Understanding SimpleChanges
SimpleChanges contains information about changed inputs, including previous value, current value, and whether this is the first change.
ngOnChanges(changes: SimpleChanges): void {const productChange = changes['productId'];if (productChange) {console.log('Previous:', productChange.previousValue);console.log('Current:', productChange.currentValue);console.log('First Change:', productChange.firstChange);}}
Real-World ngOnChanges Example
<app-user-profile [userId]="selectedUserId" />
ngOnChanges(changes: SimpleChanges): void {if (changes['userId'] && this.userId) {this.loadUser(this.userId);}}
ngOnInit
ngOnInit executes once after Angular initializes the component input properties for the initial lifecycle.
import { OnInit } from '@angular/core';export class ProductsComponent implements OnInit {ngOnInit(): void {this.loadProducts();}}
Common uses include initial API calls, state setup, form initialization, initial reactive flows, and reading route-related state.
ngOnInit Architecture
Component Created│▼Dependencies Available│▼Inputs Initialized│▼ngOnInit│▼Initialization Logic
Important: ngOnInit Runs Once
For a component instance, ngOnInit runs once. If an input changes later, ngOnInit does not run again. Use input reactivity or ngOnChanges for changing inputs.
ngDoCheck
ngDoCheck allows custom change-detection checking logic, but it may run frequently.
ngDoCheck(): void {console.log('Change detection check');}
Why ngDoCheck Can Be Dangerous
Expensive logic in ngDoCheck can seriously affect performance because it may run repeatedly.
ngDoCheck(): void {this.products = this.largeProductList.filter(product => product.active);}
Prefer reactive state derivation with Signals or RxJS when possible.
Content Projection Lifecycle
Angular supports content projection with <ng-content />. Projected content has its own lifecycle hooks.
<app-card><h2>Product Details</h2></app-card><!-- inside app-card --><div class="card"><ng-content /></div>
ngAfterContentInit
ngAfterContentInit executes once after Angular initializes projected content. It is useful with ContentChild and ContentChildren.
ContentChild Example
@ContentChild(CardTitleComponent)title!: CardTitleComponent;ngAfterContentInit(): void {console.log(this.title);}
ngAfterContentChecked
ngAfterContentChecked executes after Angular checks projected content. It can run frequently, so avoid expensive work.
View Lifecycle
After content initialization, Angular initializes the component's own view. The main view hooks are ngAfterViewInit and ngAfterViewChecked.
ngAfterViewInit
ngAfterViewInit runs once after Angular initializes the component's view and child views. It is commonly used with ViewChild and DOM-dependent third-party libraries.
@ViewChild('searchInput')searchInput!: ElementRef;ngAfterViewInit(): void {console.log(this.searchInput);}
ViewChild Lifecycle Architecture
Component Created│▼Template Created│▼View Children Initialized│▼ngAfterViewInit│▼ViewChild Available
Real-World Example: Chart Initialization
If a chart library needs a DOM container, initialize it after the view exists.
<div #chartContainer></div>
ngAfterViewInit(): void {this.initializeChart();}
ngAfterViewChecked
ngAfterViewChecked executes after Angular checks the component view and child views. Avoid expensive operations here.
A Dangerous Pattern
Do not call APIs in ngAfterViewChecked. It may run repeatedly and generate repeated network requests.
Change Detection│▼ngAfterViewChecked│▼HTTP Request│▼Data Changes│▼Change Detection│▼Another HTTP Request
ngOnDestroy
ngOnDestroy executes immediately before Angular destroys a component or directive instance. It is primarily used for cleanup.
import { OnDestroy } from '@angular/core';export class DashboardComponent implements OnDestroy {ngOnDestroy(): void {console.log('Component destroyed');}}
Why Cleanup Matters
Components may create subscriptions, timers, intervals, DOM event listeners, WebSocket connections, and third-party library instances. If those survive after destruction, they can cause memory leaks, duplicate events, and resource waste.
Cleanup Architecture
Component Created│▼Start Resource├── Subscription├── Timer├── Event Listener└── WebSocket│▼Component Destroyed│▼ngOnDestroy│▼Cleanup Resources
Timer Cleanup Example
private timerId!: number;ngOnInit(): void {this.timerId = window.setInterval(() => {console.log('Refreshing');}, 5000);}ngOnDestroy(): void {clearInterval(this.timerId);}
Subscription Cleanup
Long-lived Observables may require cleanup. A traditional approach stores the subscription and unsubscribes in ngOnDestroy.
private subscription!: Subscription;ngOnDestroy(): void {this.subscription.unsubscribe();}
Modern Cleanup with DestroyRef
DestroyRef allows cleanup logic to be registered with the current lifecycle context.
import { DestroyRef, inject } from '@angular/core';export class DashboardComponent {private destroyRef = inject(DestroyRef);constructor() {const timerId = window.setInterval(() => {console.log('Running');}, 5000);this.destroyRef.onDestroy(() => {clearInterval(timerId);});}}
takeUntilDestroyed()
takeUntilDestroyed() provides lifecycle-aware RxJS subscription cleanup.
import { DestroyRef, inject } from '@angular/core';import { takeUntilDestroyed } from '@angular/core/rxjs-interop';export class DashboardComponent {private destroyRef = inject(DestroyRef);constructor(private notificationService: NotificationService) {this.notificationService.notifications$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(notification => {console.log(notification);});}}
Why takeUntilDestroyed Is Useful
Observable│▼takeUntilDestroyed()│▼Component Lifecycle│▼Automatic Cleanup
It reduces boilerplate and the risk of forgetting subscription cleanup.
Important: Not Every Subscription Needs Manual Unsubscribe
Typical HttpClient requests emit a response and complete. Long-lived streams such as valueChanges, WebSockets, interval(), Subjects, router events, and app state streams require more lifecycle awareness.
AsyncPipe and Lifecycle Management
When an Observable is consumed in the template, AsyncPipe manages subscription lifecycle.
@if (products$ | async; as products) {...}
Lifecycle Execution Order
Constructor│▼ngOnChanges│▼ngOnInit│▼ngDoCheck│▼ngAfterContentInit│▼ngAfterContentChecked│▼ngAfterViewInit│▼ngAfterViewChecked│▼ngOnDestroy
Lifecycle Timeline
Later change-detection cycles may run ngOnChanges when relevant inputs change, followed by ngDoCheck, ngAfterContentChecked, and ngAfterViewChecked.
Parent and Child Lifecycle
Lifecycle ordering can become complex because Angular traverses the component tree. Do not depend on fragile assumptions about unrelated lifecycle timing. Communicate explicitly through inputs, outputs, Signals, services, and queries.
Real-World Example: Product Details
Component Created│▼productId = 101│▼ngOnChanges│▼Load Product 101│▼ngOnInit│▼Initialize Component State│▼ngAfterViewInit│▼Initialize Product Gallery│▼productId = 102│▼ngOnChanges│▼Load Product 102│▼ngOnDestroy│▼Cleanup
Real-World Example: Video Player
A reusable video component may receive a video URL in ngOnChanges, initialize the player in ngAfterViewInit, and destroy the player with ngOnDestroy or DestroyRef.
Real-World Example: WebSocket Dashboard
A live trading dashboard may connect to a WebSocket when created and close it when destroyed. Forgetting cleanup leaves messages and resource usage active after navigation.
Lifecycle Hooks and Signals
Modern Angular applications increasingly use Signals. Reactive logic can be expressed with signal(), computed(), and effect() rather than relying on lifecycle hooks for every state change.
productId = input.required<number>();
Lifecycle Hooks vs Reactive Programming
Use lifecycle hooks for lifecycle-specific events such as view initialization, content initialization, resource cleanup, and imperative third-party integrations. Use Signals or RxJS for data-driven reactivity.
Performance Considerations
The hooks ngDoCheck, ngAfterContentChecked, and ngAfterViewChecked can execute frequently. Avoid API calls, large array filtering, complex calculations, repeated DOM manipulation, and state updates that trigger more checks inside them.
Dangerous Lifecycle Architecture
Change Detection│▼ngAfterViewChecked│▼Update State│▼New Change Detection│▼ngAfterViewChecked│▼Update State Again
This can create performance degradation, unexpected loops, change detection errors, and difficult-to-debug behavior.
ExpressionChangedAfterItHasBeenCheckedError
ExpressionChangedAfterItHasBeenCheckedError commonly appears in development when a bound value changes after Angular has already checked it during the same change-detection cycle. Do not blindly hide it with setTimeout(); examine whether the state change belongs in a different lifecycle hook or reactive data flow.
Common Pitfalls
Calling APIs in ngAfterViewChecked
This hook may run repeatedly and generate excessive API calls.
Heavy Logic in ngDoCheck
Custom checks should be lightweight. Prefer reactive patterns whenever possible.
Forgetting Resource Cleanup
Clean up timers, long-lived subscriptions, WebSockets, event listeners, and third-party instances.
Assuming ngOnInit Runs on Every Input Change
It runs once per component instance. Use input reactivity or ngOnChanges.
Accessing ViewChild Too Early
If logic depends on a view query, use the appropriate view lifecycle or modern query APIs.
Using Every Lifecycle Hook
Use only the hooks required by the component.
Manually Unsubscribing from Everything
Understand whether the Observable completes naturally and use AsyncPipe or takeUntilDestroyed() when appropriate.
Enterprise Best Practices
- Keep initialization focused and use
ngOnInitfor clear component setup responsibilities. - Prefer reactive data flow with Signals, computed Signals, RxJS, and
AsyncPipe. - Use modern cleanup APIs such as
DestroyRefandtakeUntilDestroyed(). - Keep checked hooks lightweight.
- Destroy third-party libraries such as charts, maps, editors, media players, and WebSocket clients.
- Avoid lifecycle-driven business logic; keep business rules in services, domain layers, or state management.
Common Misconceptions
Misconception
The constructor and ngOnInit are the same.
Reality
The constructor creates the class instance and supports dependency injection. ngOnInit is an Angular lifecycle hook for component initialization.
Misconception
ngOnInit runs every time an input changes.
Reality
ngOnInit runs once for each component instance. Use ngOnChanges or input reactivity for input changes.
Misconception
Every subscription must manually unsubscribe in ngOnDestroy.
Reality
Some streams complete naturally. Others can be managed with AsyncPipe or takeUntilDestroyed().
Misconception
ngAfterViewChecked is a good place for API calls.
Reality
It can execute frequently and may trigger repeated network requests.
Misconception
Lifecycle hooks are the best way to react to every data change.
Reality
Signals and RxJS often provide cleaner reactive solutions for data-driven changes.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1BeginnerQuestionWhat are Angular Lifecycle Hooks?+
Answer
2BeginnerQuestionWhat is the difference between constructor and ngOnInit?+
Answer
3BeginnerQuestionWhen does ngOnChanges execute?+
Answer
4BeginnerQuestionWhich executes first: ngOnChanges or ngOnInit?+
Answer
5BeginnerQuestionDoes ngOnInit run multiple times?+
Answer
6BeginnerQuestionWhat is ngDoCheck?+
Answer
7IntermediateQuestionWhat is ngAfterViewInit used for?+
Answer
8IntermediateQuestionWhat is the difference between ngAfterContentInit and ngAfterViewInit?+
Answer
9IntermediateQuestionWhat is ngOnDestroy used for?+
Answer
10IntermediateQuestionWhat is DestroyRef?+
Answer
11IntermediateQuestionWhat is takeUntilDestroyed()?+
Answer
12IntermediateQuestionDo HttpClient subscriptions need manual cleanup?+
Answer
13AdvancedQuestionWhy shouldn't we call APIs in ngAfterViewChecked?+
Answer
14AdvancedQuestionHow would you initialize a chart library in Angular?+
Answer
15AdvancedQuestionHow would you manage a WebSocket subscription?+
Answer
16AdvancedQuestionWhat lifecycle hooks should avoid expensive logic?+
Answer
17AdvancedQuestionHow have Signals changed Angular lifecycle patterns?+
Answer
18AdvancedQuestionWhat is the lifecycle order in Angular?+
Answer
Summary
Angular Lifecycle Hooks allow your code to participate in the lifecycle of components and directives.
Component Creation│▼Constructor│▼ngOnChanges│▼ngOnInit│▼ngDoCheck│▼ngAfterContentInit│▼ngAfterContentChecked│▼ngAfterViewInit│▼ngAfterViewChecked│▼Component Active│▼ngOnDestroy│▼Cleanup
Use lifecycle hooks for lifecycle-specific responsibilities, and use reactive programming for data-driven changes. Understanding that distinction helps build Angular applications that are easier to maintain, more performant, and less likely to leak resources.
Next Lesson: Angular Styling - you'll learn component-scoped styles, global styles, style encapsulation, dynamic classes and styles, CSS variables, responsive design, theming, dark mode, design systems, and scalable styling architecture.