Angular Tutorial 0/42 lessons ~6 min read Lesson 19

    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.

    Course progress0%
    Focus
    52 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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 constructor and ngOnInit.
    • How ngOnChanges, ngOnInit, ngDoCheck, content hooks, view hooks, and ngOnDestroy work.
    • How to safely access ViewChild and projected content.
    • How to clean up subscriptions, timers, event listeners, WebSockets, and third-party resources.
    • How modern Angular uses DestroyRef, takeUntilDestroyed(), Signals, and AsyncPipe.

    Introduction

    Every Angular component has a lifecycle. Angular creates it, initializes it, renders it, checks it for changes, updates it, and eventually destroys it.

    text
    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.

    text
    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.

    text
    ngOnChanges
    ngOnInit
    ngDoCheck
    ngAfterContentInit
    ngAfterContentChecked
    ngAfterViewInit
    ngAfterViewChecked
    ngOnDestroy

    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.

    typescript
    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.

    text
    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.

    typescript
    constructor(
    private productService: ProductService
    ) {}
    ngOnInit(): void {
    this.loadProducts();
    }

    ngOnChanges

    ngOnChanges executes when Angular sets or changes bound input properties.

    typescript
    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.

    typescript
    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

    html
    <app-user-profile [userId]="selectedUserId" />
    typescript
    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.

    typescript
    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

    text
    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.

    typescript
    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.

    typescript
    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.

    html
    <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

    typescript
    @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.

    typescript
    @ViewChild('searchInput')
    searchInput!: ElementRef;
    ngAfterViewInit(): void {
    console.log(this.searchInput);
    }

    ViewChild Lifecycle Architecture

    text
    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.

    html
    <div #chartContainer></div>
    typescript
    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.

    text
    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.

    typescript
    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

    text
    Component Created
    Start Resource
    ├── Subscription
    ├── Timer
    ├── Event Listener
    └── WebSocket
    Component Destroyed
    ngOnDestroy
    Cleanup Resources

    Timer Cleanup Example

    typescript
    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.

    typescript
    private subscription!: Subscription;
    ngOnDestroy(): void {
    this.subscription.unsubscribe();
    }

    Modern Cleanup with DestroyRef

    DestroyRef allows cleanup logic to be registered with the current lifecycle context.

    typescript
    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.

    typescript
    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

    text
    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.

    html
    @if (products$ | async; as products) {
    ...
    }

    Lifecycle Execution Order

    text
    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

    text
    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.

    typescript
    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

    text
    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 ngOnInit for clear component setup responsibilities.
    • Prefer reactive data flow with Signals, computed Signals, RxJS, and AsyncPipe.
    • Use modern cleanup APIs such as DestroyRef and takeUntilDestroyed().
    • 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.

    18 questions
    1BeginnerQuestionWhat are Angular Lifecycle Hooks?+

    Answer

    Lifecycle Hooks are methods Angular invokes at specific stages of a component or directive lifecycle, allowing initialization, change handling, view/content logic, and cleanup.
    2BeginnerQuestionWhat is the difference between constructor and ngOnInit?+

    Answer

    The constructor creates the class instance and is commonly used for Dependency Injection. ngOnInit runs after Angular initializes inputs for the first lifecycle pass.
    3BeginnerQuestionWhen does ngOnChanges execute?+

    Answer

    ngOnChanges executes when Angular sets or changes bound input properties and receives a SimpleChanges object.
    4BeginnerQuestionWhich executes first: ngOnChanges or ngOnInit?+

    Answer

    For a component with bound inputs during initial creation, ngOnChanges runs before ngOnInit.
    5BeginnerQuestionDoes ngOnInit run multiple times?+

    Answer

    No. It runs once for each component instance. A recreated component gets its own ngOnInit.
    6BeginnerQuestionWhat is ngDoCheck?+

    Answer

    ngDoCheck allows custom change-detection checking logic, but it can execute frequently, so expensive operations should be avoided.
    7IntermediateQuestionWhat is ngAfterViewInit used for?+

    Answer

    It runs after the component view and child views are initialized, commonly for ViewChild access or DOM-dependent third-party libraries.
    8IntermediateQuestionWhat is the difference between ngAfterContentInit and ngAfterViewInit?+

    Answer

    ngAfterContentInit relates to projected content through ng-content. ngAfterViewInit relates to the component's own view and child views.
    9IntermediateQuestionWhat is ngOnDestroy used for?+

    Answer

    ngOnDestroy is used for cleanup before a component or directive is destroyed, including subscriptions, timers, event listeners, WebSockets, and third-party instances.
    10IntermediateQuestionWhat is DestroyRef?+

    Answer

    DestroyRef is a modern Angular API that registers cleanup callbacks with the lifecycle of the current injection context.
    11IntermediateQuestionWhat is takeUntilDestroyed()?+

    Answer

    takeUntilDestroyed() is an Angular RxJS interoperability utility that completes a stream when the associated lifecycle context is destroyed.
    12IntermediateQuestionDo HttpClient subscriptions need manual cleanup?+

    Answer

    Typical HttpClient requests emit and complete automatically, so they usually do not require manual unsubscription solely for memory leaks. Long-lived streams require more care.
    13AdvancedQuestionWhy shouldn't we call APIs in ngAfterViewChecked?+

    Answer

    ngAfterViewChecked may run repeatedly during change detection, causing repeated network requests and extra checks.
    14AdvancedQuestionHow would you initialize a chart library in Angular?+

    Answer

    Initialize it after the DOM element exists, often in ngAfterViewInit, and destroy the chart instance when the component is destroyed.
    15AdvancedQuestionHow would you manage a WebSocket subscription?+

    Answer

    Use an appropriate service, expose data reactively, and clean up with takeUntilDestroyed(), DestroyRef, or service-level lifecycle management.
    16AdvancedQuestionWhat lifecycle hooks should avoid expensive logic?+

    Answer

    Be especially careful with ngDoCheck, ngAfterContentChecked, and ngAfterViewChecked because they can execute frequently.
    17AdvancedQuestionHow have Signals changed Angular lifecycle patterns?+

    Answer

    Signals allow data-driven reactivity through computed() and effect(), reducing the need for lifecycle hooks for many state-change scenarios.
    18AdvancedQuestionWhat is the lifecycle order in Angular?+

    Answer

    A simplified first pass is constructor, ngOnChanges, ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked, ngAfterViewInit, ngAfterViewChecked, then ngOnDestroy before destruction.

    Summary

    Angular Lifecycle Hooks allow your code to participate in the lifecycle of components and directives.

    text
    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.

    Ready to mark this lesson complete?Track your journey across the entire course.