Skip to content

Angular Component Lifecycle

Every Angular component moves through a predictable set of lifecycle stages, from creation to destruction. This lesson covers each lifecycle hook, in the order Angular calls them, with practical examples.

What Is the Component Lifecycle?

Angular manages the full lifecycle of every component instance: creating it, checking and updating its inputs, rendering its view, responding to changes, and eventually destroying it when it's removed from the DOM. Lifecycle hooks are methods you implement to run code at specific points in that process.

Hooks are optional, implement only the ones you actually need. The two most commonly used are ngOnInit (setup logic) and ngOnDestroy (cleanup logic).

import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({ selector: 'app-timer', standalone: true, template: '{{ seconds }}s' })
export class TimerComponent implements OnInit, OnDestroy {
  seconds = 0;
  private intervalId?: number;

  ngOnInit() {
    this.intervalId = setInterval(() => this.seconds++, 1000);
  }

  ngOnDestroy() {
    clearInterval(this.intervalId);
  }
}

ngOnInit starts the timer once the component is initialized; ngOnDestroy clears it to avoid leaking a running interval.

Lifecycle Hook Order

constructor()
ngOnChanges()   // before ngOnInit, and on every input change
ngOnInit()      // once, after the first ngOnChanges
ngDoCheck()     // on every change detection run
ngAfterContentInit()
ngAfterContentChecked()
ngAfterViewInit()
ngAfterViewChecked()
ngOnDestroy()   // once, just before Angular destroys the component
  • Implement a hook by adding its corresponding interface (OnInit, OnDestroy, etc.) and method to your class.
  • ngOnChanges runs before ngOnInit and again whenever a bound @Input() changes.
  • ngAfterViewInit is the first point where child component views and @ViewChild references are guaranteed available.
  • ngOnDestroy is your one chance to clean up subscriptions, timers, and listeners before the component is removed.

Lifecycle Hooks Cheatsheet

When each hook runs and what it's typically used for.

Hook Runs When Typical Use
ngOnChanges An @Input() value changes React to new input values
ngOnInit Once, after the first input check Initial data fetch, setup
ngDoCheck Every change detection run Custom, manual change detection
ngAfterContentInit Once, after projected content is initialized Work with @ContentChild
ngAfterContentChecked After every content check React to projected content changes
ngAfterViewInit Once, after the view (and children) is initialized Work with @ViewChild
ngAfterViewChecked After every view check React to view changes (use sparingly)
ngOnDestroy Just before the component is destroyed Unsubscribe, clear timers/listeners

ngOnInit vs the Constructor

The constructor is plain TypeScript/JavaScript and runs before Angular has set any @Input() values or initialized the component's dependency injection context fully for view-related work. ngOnInit runs after Angular has processed the first round of inputs, making it the right place for setup logic that depends on those inputs.

  • Use the constructor only for simple dependency injection (private http = inject(HttpClient)).
  • Use ngOnInit for initial API calls, subscriptions, or setup logic depending on @Input() values.
  • Avoid heavy logic in the constructor, it can make components harder to test and instantiate.

Reacting to Input Changes With ngOnChanges

ngOnChanges receives a SimpleChanges object describing which inputs changed, along with their previous and current values, useful when a component needs to respond specifically to an input update rather than just reading the latest value.

import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';

@Component({ selector: 'app-progress', standalone: true, template: '{{ percent }}%' })
export class ProgressComponent implements OnChanges {
  @Input() percent = 0;

  ngOnChanges(changes: SimpleChanges) {
    if (changes['percent']) {
      console.log('Previous:', changes['percent'].previousValue);
      console.log('Current:', changes['percent'].currentValue);
    }
  }
}

Cleaning Up in ngOnDestroy

Anything a component sets up that outlives a single render, subscriptions, setInterval/setTimeout timers, or manually attached event listeners, must be cleaned up in ngOnDestroy, otherwise it keeps running (and potentially leaking memory) after the component is gone.

export class NotificationsComponent implements OnInit, OnDestroy {
  private subscription?: Subscription;

  ngOnInit() {
    this.subscription = this.notifications$.subscribe(n => this.list.push(n));
  }

  ngOnDestroy() {
    this.subscription?.unsubscribe();
  }
}

Common Mistakes

  • Doing initial data fetching in the constructor instead of ngOnInit.
  • Forgetting to unsubscribe from Observables in ngOnDestroy, causing memory leaks and stale updates.
  • Reading @ViewChild references before ngAfterViewInit has run, when they are still undefined.
  • Overusing ngDoCheck/ngAfterViewChecked for logic that runs on every single change detection cycle, hurting performance.

Key Takeaways

  • Angular calls lifecycle hooks in a fixed order: changes, init, content, view, and eventually destroy.
  • ngOnInit is the standard place for setup logic that depends on component inputs.
  • ngOnDestroy is essential for cleaning up subscriptions, timers, and listeners.
  • ngAfterViewInit is the first safe point to read @ViewChild references.

Pro Tip

For components using signals exclusively, consider effect() for reactive side effects rather than manually orchestrating logic across multiple lifecycle hooks, it can simplify code that reacts to changing state.