Angular Lifecycle

Read one Angular interview question at a time, then flip for the complete explanation, example, and code.

0/60 Known filtered set
Level
Read Full Guide Question: 1 of 60

Angular Interview Questions · Angular Lifecycle Companion Article

Angular Lifecycle – Complete Practical Guide

Angular components are not created once and then left unchanged. A component passes through several stages: Angular creates it, initializes its inputs, checks it for changes, renders its content and view, updates it when application state changes, and finally destroys it when it is no longer required.

Angular provides lifecycle hooks that allow developers to run code at specific points during this process.

Understanding the lifecycle is important when working with:

  • API calls
  • component inputs
  • DOM elements
  • projected content
  • subscriptions
  • timers
  • event listeners
  • third-party JavaScript libraries
  • component cleanup
  • performance-sensitive applications

Component Lifecycle

A component lifecycle describes everything that happens from the moment Angular creates a component until that component is destroyed.

The main lifecycle stages are:

  1. Component creation
  2. Input initialization
  3. Change detection
  4. Content initialization
  5. View initialization
  6. DOM rendering
  7. Repeated change checks
  8. Component destruction

The commonly used lifecycle methods and render callbacks are:

Text
constructor
    ↓
ngOnChanges
    ↓
ngOnInit
    ↓
ngDoCheck
    ↓
ngAfterContentInit
    ↓
ngAfterContentChecked
    ↓
ngAfterViewInit
    ↓
ngAfterViewChecked
    ↓
DOM Render
    ↓
afterNextRender / afterEveryRender
    ↓
ngOnDestroy

During initial component creation, ngOnChanges runs before ngOnInit when the component has inputs. Hooks such as ngDoCheck, ngAfterContentChecked, and ngAfterViewChecked can run repeatedly during later change-detection cycles.

Lifecycle Hooks at a Glance

Lifecycle APIRunsCommon Purpose
constructorWhen the class instance is createdDependency injection and basic initialization
ngOnChangesWhen input values changeReact to parent-provided data
ngOnInitOnce after initial inputs are initializedComponent initialization
ngDoCheckDuring component change checkingCustom change detection
ngAfterContentInitOnce after projected content initializesWork with content queries
ngAfterContentCheckedAfter projected content is checkedObserve projected-content changes
ngAfterViewInitOnce after component view initializesWork with view elements
ngAfterViewCheckedAfter component view is checkedObserve view updates
afterNextRenderOnce after the next application renderOne-time DOM work
afterEveryRenderAfter every application renderRepeated DOM-related work
ngOnDestroyOnce before destructionCleanup resources

constructor

The constructor is technically a JavaScript/TypeScript class constructor rather than an Angular lifecycle hook.

Angular calls it when creating the component instance.

A constructor is commonly used for:

  • dependency injection
  • simple class initialization
  • registering setup that requires an injection context

Example:

TypeScript
import { Component } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app-user',
  template: `<p>User Component</p>`
})
export class UserComponent {

  constructor(private userService: UserService) {
    console.log('Component instance created');
  }
}

What Should Not Usually Go in the Constructor?

Avoid putting large amounts of component initialization logic inside the constructor.

For example, avoid:

TypeScript
constructor(private userService: UserService) {
  this.userService.loadUsers().subscribe(...);
  this.calculateDashboard();
  this.initializePage();
}

Component initialization that depends on initialized inputs generally belongs in ngOnInit.

A useful rule is:

Text
constructor → create the class and inject dependencies
ngOnInit    → initialize component behavior

ngOnInit

ngOnInit() runs once after Angular has initialized the component's input values.

It is one of the most commonly used lifecycle hooks.

Typical uses include:

  • loading initial data
  • initializing component state
  • starting initial business logic
  • using values received through inputs
  • setting up data required by the template

Example:

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

@Component({
  selector: 'app-product',
  template: `<h2>{{ title }}</h2>`
})
export class ProductComponent implements OnInit {

  title = '';

  ngOnInit(): void {
    this.title = 'Products';
  }
}

A common API-related example is:

TypeScript
ngOnInit(): void {
  this.loadProducts();
}

loadProducts(): void {
  this.productService.getProducts().subscribe(products => {
    this.products = products;
  });
}

ngOnInit executes once for each component instance. It does not execute again every time Angular checks the component.

constructor vs ngOnInit

These two are often confused.

constructorngOnInit
JavaScript/TypeScript class featureAngular lifecycle hook
Runs when instance is createdRuns after initial inputs are initialized
Best for dependency injectionBest for component initialization
Inputs should not be relied upon as initialized component stateInitial input values are available
Runs firstRuns later

Example:

TypeScript
constructor() {
  console.log('Constructor');
}

ngOnInit(): void {
  console.log('ngOnInit');
}

Output:

Text
Constructor
ngOnInit

ngOnChanges

ngOnChanges() allows a component to react when one or more of its input properties change.

Consider a child component:

TypeScript
@Component({
  selector: 'app-user',
  template: `<p>{{ userName }}</p>`
})
export class UserComponent {
  @Input() userName = '';
}

When the parent changes userName, Angular can call ngOnChanges.

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

export class UserComponent implements OnChanges {

  @Input() userName = '';

  ngOnChanges(changes: SimpleChanges): void {
    console.log(changes);
  }
}

Each changed input is represented by a SimpleChange.

It provides information such as:

  • previous value
  • current value
  • whether this is the first change

Example:

TypeScript
ngOnChanges(changes: SimpleChanges): void {

  if (changes['userName']) {

    console.log(
      'Previous:',
      changes['userName'].previousValue
    );

    console.log(
      'Current:',
      changes['userName'].currentValue
    );

    console.log(
      'First Change:',
      changes['userName'].firstChange
    );
  }
}

A practical use case is recalculating something whenever an input changes:

TypeScript
@Input() price = 0;
@Input() quantity = 0;

ngOnChanges(): void {
  this.total = this.price * this.quantity;
}

During initialization, the first ngOnChanges occurs before ngOnInit.

ngDoCheck

ngDoCheck() runs whenever Angular checks the component for changes.

It gives developers an opportunity to implement custom checking logic.

Example:

TypeScript
import { Component, DoCheck } from '@angular/core';

export class CartComponent implements DoCheck {

  ngDoCheck(): void {
    console.log('Component checked');
  }
}

This hook can execute very frequently.

For that reason, code inside it should be extremely lightweight.

Avoid expensive operations such as:

TypeScript
ngDoCheck(): void {
  this.calculateThousandsOfRecords();
}

This could significantly reduce application performance because Angular may call the method many times.

Use ngDoCheck only when Angular's normal reactive mechanisms cannot conveniently handle the required change.

Angular's current guidance specifically warns that ngDoCheck runs frequently and should generally be avoided unless necessary.

ngAfterContentInit

To understand ngAfterContentInit, first understand projected content.

A reusable component may contain:

HTML
<ng-content></ng-content>

The parent can provide content:

HTML
<app-card>
  <h2>Angular Lifecycle</h2>
</app-card>

The <h2> is content projected into app-card.

ngAfterContentInit() executes once after Angular initializes this projected content.

Example:

TypeScript
import {
  AfterContentInit,
  Component
} from '@angular/core';

export class CardComponent implements AfterContentInit {

  ngAfterContentInit(): void {
    console.log('Projected content initialized');
  }
}

It is especially useful when working with content queries such as:

TypeScript
@ContentChild(...)

A simple mental model is:

Text
Content = HTML supplied from outside the component

View = HTML belonging to the component's own template

Angular warns against changing checked component state from this stage because doing so can result in ExpressionChangedAfterItHasBeenCheckedError.

ngAfterContentChecked

ngAfterContentChecked() executes after Angular checks the content projected into the component.

Example:

TypeScript
import {
  AfterContentChecked,
  Component
} from '@angular/core';

export class CardComponent implements AfterContentChecked {

  ngAfterContentChecked(): void {
    console.log('Projected content checked');
  }
}

Unlike ngAfterContentInit, this hook can run many times.

Compare:

Text
ngAfterContentInit
        ↓
Runs once

ngAfterContentChecked
        ↓
May run repeatedly

Avoid expensive processing here.

For example, this is poor practice:

TypeScript
ngAfterContentChecked(): void {
  this.processLargeDataset();
}

Hooks that execute during every change-detection cycle should normally contain minimal logic.

ngAfterViewInit

ngAfterViewInit() runs once after Angular has initialized the component's own view.

It is commonly associated with access to elements or child components from the component template.

Example template:

HTML
<input #searchBox type="text">

Component:

TypeScript
import {
  AfterViewInit,
  Component,
  ElementRef,
  ViewChild
} from '@angular/core';

export class SearchComponent implements AfterViewInit {

  @ViewChild('searchBox')
  searchBox!: ElementRef<HTMLInputElement>;

  ngAfterViewInit(): void {
    this.searchBox.nativeElement.focus();
  }
}

Typical use cases include:

  • accessing view queries
  • reading a child component
  • integrating UI libraries
  • interacting with elements after view initialization

However, DOM-specific work can often be better expressed through Angular's modern render callbacks when the operation specifically needs the rendered DOM.

ngAfterViewInit executes only once for each created view. Angular also cautions against changing already-checked state from this hook because that can cause ExpressionChangedAfterItHasBeenCheckedError.

ngAfterViewChecked

ngAfterViewChecked() executes after Angular checks the component's view.

Example:

TypeScript
import {
  AfterViewChecked,
  Component
} from '@angular/core';

export class DashboardComponent implements AfterViewChecked {

  ngAfterViewChecked(): void {
    console.log('View checked');
  }
}

This method can execute frequently.

Therefore, avoid performing:

  • HTTP requests
  • complex calculations
  • heavy array processing
  • repeated DOM manipulation
  • unnecessary state changes

inside this hook.

For example, never treat it like an initialization hook:

TypeScript
ngAfterViewChecked(): void {
  this.http.get('/api/products').subscribe();
}

That logic could execute repeatedly as Angular checks the view.

Angular recommends avoiding this hook unless there is no cleaner alternative because of its potential performance impact.

Content Lifecycle vs View Lifecycle

The difference between content and view is important.

Consider:

HTML
<app-panel>
  <p>Projected paragraph</p>
</app-panel>

And inside app-panel:

HTML
<section>
  <ng-content></ng-content>

  <button>Save</button>
</section>

Here:

Text
<p>Projected paragraph</p>

belongs to projected content.

But:

Text
<section>
<button>

belong to the component's view.

Therefore:

Text
ngAfterContentInit
ngAfterContentChecked

are related to projected content.

While:

Text
ngAfterViewInit
ngAfterViewChecked

are related to the component's view.

ngOnDestroy

ngOnDestroy() executes once immediately before Angular destroys a component.

A component may be destroyed when:

  • the user navigates to another route
  • an @if condition removes it
  • a parent component is destroyed
  • a dynamically created component is removed

Example:

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

export class NotificationComponent implements OnDestroy {

  ngOnDestroy(): void {
    console.log('Component destroyed');
  }
}

Its main purpose is cleanup.

Examples of resources that may require cleanup include:

  • manually managed subscriptions
  • timers
  • intervals
  • DOM event listeners
  • WebSocket connections
  • observers
  • third-party library instances

Angular calls ngOnDestroy once before destroying the component instance.

Lifecycle Cleanup

Cleanup is one of the most important parts of component lifecycle management.

Suppose a component creates an interval:

TypeScript
timerId!: ReturnType<typeof setInterval>;

ngOnInit(): void {
  this.timerId = setInterval(() => {
    console.log('Running');
  }, 1000);
}

If the component is destroyed but the interval continues running, unnecessary work continues in the background.

The interval should therefore be cleared:

TypeScript
ngOnDestroy(): void {
  clearInterval(this.timerId);
}

The complete pattern becomes:

TypeScript
export class TimerComponent implements OnInit, OnDestroy {

  timerId!: ReturnType<typeof setInterval>;

  ngOnInit(): void {

    this.timerId = setInterval(() => {
      console.log('Running');
    }, 1000);
  }

  ngOnDestroy(): void {
    clearInterval(this.timerId);
  }
}

Cleaning Up Event Listeners

Manual event listeners should also be removed when they are no longer needed.

Example:

TypeScript
private handleResize = () => {
  console.log(window.innerWidth);
};

ngOnInit(): void {
  window.addEventListener('resize', this.handleResize);
}

ngOnDestroy(): void {
  window.removeEventListener('resize', this.handleResize);
}

Without cleanup, callbacks can continue referencing resources that should no longer be used.

Cleaning Up Subscriptions

Some manually created RxJS subscriptions may need to be unsubscribed.

Traditional example:

TypeScript
private subscription!: Subscription;

ngOnInit(): void {

  this.subscription =
    this.userService.getUsers().subscribe(users => {
      this.users = users;
    });
}

ngOnDestroy(): void {
  this.subscription.unsubscribe();
}

Modern Angular applications can often use takeUntilDestroyed() instead of maintaining subscription variables manually.

DestroyRef

DestroyRef provides a modern Angular API for registering cleanup logic.

Instead of placing every cleanup operation inside one large ngOnDestroy() method, cleanup can be registered close to the code that creates the resource.

Example:

TypeScript
import {
  Component,
  DestroyRef,
  inject
} from '@angular/core';

@Component({
  selector: 'app-timer',
  template: `<p>Timer</p>`
})
export class TimerComponent {

  private destroyRef = inject(DestroyRef);

  constructor() {

    const timerId = setInterval(() => {
      console.log('Running');
    }, 1000);

    this.destroyRef.onDestroy(() => {
      clearInterval(timerId);
    });
  }
}

This keeps setup and cleanup logic together:

Text
Create resource
      ↓
Register cleanup
      ↓
Component destroyed
      ↓
Cleanup automatically executed

DestroyRef.onDestroy() registers a callback in the current destruction scope. DestroyRef also exposes a destroyed property that can be checked when asynchronous code might finish after the component has already been destroyed.

DestroyRef with Asynchronous Logic

Consider delayed work:

TypeScript
private destroyRef = inject(DestroyRef);

loadData(): void {

  setTimeout(() => {

    if (this.destroyRef.destroyed) {
      return;
    }

    this.message = 'Data loaded';

  }, 3000);
}

Checking the destroyed state can help prevent code from trying to interact with a component that no longer exists.

takeUntilDestroyed()

Angular also provides takeUntilDestroyed() for RxJS integration.

It completes the observable subscription when the associated Angular destruction context is destroyed.

Example:

TypeScript
import { Component } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-user-list',
  template: `...`
})
export class UserListComponent {

  constructor(private userService: UserService) {

    this.userService.getUsers()
      .pipe(takeUntilDestroyed())
      .subscribe(users => {
        console.log(users);
      });
  }
}

This avoids manually writing:

TypeScript
private subscription!: Subscription;

ngOnDestroy(): void {
  this.subscription.unsubscribe();
}

For many Angular/RxJS scenarios, this results in simpler lifecycle cleanup.

afterNextRender

afterNextRender() is a modern Angular render callback.

It executes after Angular next finishes rendering the application to the DOM.

Unlike methods such as ngAfterViewInit, it is:

  • a standalone function
  • not an interface method
  • associated with application rendering
  • useful when work specifically requires rendered DOM

Example:

TypeScript
import {
  afterNextRender,
  Component
} from '@angular/core';

@Component({
  selector: 'app-profile',
  template: `<div>Profile</div>`
})
export class ProfileComponent {

  constructor() {

    afterNextRender(() => {
      console.log('Next render completed');
    });
  }
}

A typical use case is initializing a third-party JavaScript library after the DOM has been rendered.

afterNextRender must normally be registered from an Angular injection context, such as a component constructor. Render callbacks run in browser environments and do not execute during server-side rendering or build-time pre-rendering.

When to Use afterNextRender

Use it when something should happen once after the next completed render.

Examples include:

  • initializing a chart library
  • measuring an element after rendering
  • initializing a third-party widget
  • performing controlled DOM work
  • reading layout information

Conceptually:

Text
Component created
      ↓
Angular performs rendering
      ↓
DOM updated
      ↓
afterNextRender callback

afterEveryRender

afterEveryRender() executes after each completed Angular application render.

Example:

TypeScript
import {
  afterEveryRender,
  Component
} from '@angular/core';

@Component({
  selector: 'app-dashboard',
  template: `<div>Dashboard</div>`
})
export class DashboardComponent {

  constructor() {

    afterEveryRender(() => {
      console.log('Application rendered');
    });
  }
}

The important difference is:

Text
afterNextRender
      ↓
Runs once after the next render

afterEveryRender
      ↓
Runs after every render

Because afterEveryRender can run repeatedly, the callback should be lightweight.

Like afterNextRender, it is a render callback rather than a traditional component lifecycle method, requires an appropriate injection context when registered normally, and runs only on browser platforms.

Render Phases

Modern Angular render callbacks support phases for organizing DOM work.

The available phases are:

  1. earlyRead
  2. write
  3. mixedReadWrite
  4. read

Angular recommends separating DOM writes and reads when possible because repeatedly mixing layout reads and writes can hurt browser rendering performance.

Example:

TypeScript
afterNextRender({
  write: () => {

    this.element.nativeElement.style.height = '200px';

    return true;
  },

  read: (changed) => {

    if (changed) {

      const height =
        this.element.nativeElement
          .getBoundingClientRect()
          .height;

      console.log(height);
    }
  }
});

The basic idea is:

Text
Write DOM changes first
        ↓
Browser layout
        ↓
Read measurements afterward

This can help avoid unnecessary layout recalculation.

afterNextRender vs afterEveryRender

FeatureafterNextRenderafterEveryRender
ExecutionOnceRepeatedly
TriggerNext completed renderEvery completed render
TypeStandalone functionStandalone function
DOM workYesYes
Browser onlyYesYes
SSR executionNoNo
Typical purposeInitialize something onceRespond to repeated rendering

ngAfterViewInit vs afterNextRender

These APIs can appear similar but solve different problems.

ngAfterViewInit

Use it when your logic is directly related to initialization of that component's view or view queries.

TypeScript
ngAfterViewInit(): void {
  console.log(this.childComponent);
}

afterNextRender

Use it when work should happen after the Angular application has completed its next DOM render.

TypeScript
constructor() {

  afterNextRender(() => {
    console.log('DOM rendering finished');
  });
}

A practical guideline is:

Text
Need initialized component view/query?
→ ngAfterViewInit

Need work after actual application DOM rendering?
→ afterNextRender

Complete Lifecycle Example

The following component demonstrates traditional lifecycle hooks together:

TypeScript
import {
  AfterContentChecked,
  AfterContentInit,
  AfterViewChecked,
  AfterViewInit,
  Component,
  DoCheck,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  SimpleChanges
} from '@angular/core';

@Component({
  selector: 'app-lifecycle-demo',
  template: `<p>{{ message }}</p>`
})
export class LifecycleDemoComponent
  implements
    OnChanges,
    OnInit,
    DoCheck,
    AfterContentInit,
    AfterContentChecked,
    AfterViewInit,
    AfterViewChecked,
    OnDestroy {

  @Input() message = '';

  constructor() {
    console.log('constructor');
  }

  ngOnChanges(changes: SimpleChanges): void {
    console.log('ngOnChanges', changes);
  }

  ngOnInit(): void {
    console.log('ngOnInit');
  }

  ngDoCheck(): void {
    console.log('ngDoCheck');
  }

  ngAfterContentInit(): void {
    console.log('ngAfterContentInit');
  }

  ngAfterContentChecked(): void {
    console.log('ngAfterContentChecked');
  }

  ngAfterViewInit(): void {
    console.log('ngAfterViewInit');
  }

  ngAfterViewChecked(): void {
    console.log('ngAfterViewChecked');
  }

  ngOnDestroy(): void {
    console.log('ngOnDestroy');
  }
}

A typical initial sequence is:

Text
constructor
ngOnChanges
ngOnInit
ngDoCheck
ngAfterContentInit
ngAfterContentChecked
ngAfterViewInit
ngAfterViewChecked

On later checks, hooks such as these can execute again:

Text
ngOnChanges   → when relevant inputs changed
ngDoCheck
ngAfterContentChecked
ngAfterViewChecked

Finally:

Text
ngOnDestroy

executes before Angular destroys the component.

Lifecycle in a Parent-Child Scenario

Suppose the parent contains:

HTML
<app-child [name]="userName"></app-child>

When Angular creates the child:

Text
Child constructor
       ↓
Input value assigned
       ↓
Child ngOnChanges
       ↓
Child ngOnInit
       ↓
Child view initialized

Later the parent changes:

TypeScript
this.userName = 'Rahul';

The child already exists, so Angular does not create it again.

Instead:

Text
Input changed
     ↓
ngOnChanges
     ↓
Normal change checking

This distinction is important.

Changing an input does not recreate the component.

Lifecycle with @if

Consider:

HTML
@if (showProfile) {
  <app-profile />
}

When:

TypeScript
showProfile = true;

Angular creates the component and its initialization lifecycle begins.

When:

TypeScript
showProfile = false;

Angular removes the component and its destruction lifecycle runs.

Conceptually:

Text
showProfile = true
       ↓
Component created
       ↓
Initialization hooks

showProfile = false
       ↓
Component removed
       ↓
ngOnDestroy / DestroyRef cleanup

Common Lifecycle Mistakes

1. Calling APIs from Frequently Executed Hooks

Avoid:

TypeScript
ngAfterViewChecked(): void {
  this.loadUsers();
}

This can cause repeated API requests.

Prefer initialization or explicit reactive logic.

2. Putting Too Much Logic in the Constructor

Avoid treating the constructor as the component's main initialization method.

Prefer:

TypeScript
constructor(private service: UserService) {}

ngOnInit(): void {
  this.loadUsers();
}

3. Forgetting Cleanup

If you manually create:

Text
intervals
listeners
observers
subscriptions
third-party objects

consider how they should be destroyed.

Use:

Text
ngOnDestroy
DestroyRef
takeUntilDestroyed

where appropriate.

4. Performing Heavy Work in ngDoCheck

Because ngDoCheck can run frequently, complex processing can create serious performance problems.

Avoid:

TypeScript
ngDoCheck(): void {

  this.products
    .filter(...)
    .map(...)
    .sort(...);
}

Prefer computed or reactive approaches where possible.

5. Updating State from Checked Hooks Without Understanding Change Detection

Hooks such as:

Text
ngAfterContentInit
ngAfterContentChecked
ngAfterViewInit
ngAfterViewChecked

occur during sensitive stages of Angular's checking process.

Changing template-bound state from these stages can lead to Angular's ExpressionChangedAfterItHasBeenCheckedError.

Angular specifically cautions against state changes from these hooks after values have already been checked.

6. Assuming Render Callbacks Run on the Server

Do not rely on:

Text
afterNextRender
afterEveryRender

for server-side rendering logic.

These callbacks execute on browser platforms rather than during SSR or build-time pre-rendering.

Which Lifecycle Hook Should You Use?

Need initial component setup?

Use:

Text
ngOnInit

Need to react when an input changes?

Use:

Text
ngOnChanges

Need custom checking behavior?

Consider:

Text
ngDoCheck

but use it sparingly.

Need projected content?

Use:

Text
ngAfterContentInit

or, rarely:

Text
ngAfterContentChecked

Need initialized view queries?

Use:

Text
ngAfterViewInit

Need repeated view-check logic?

Use:

Text
ngAfterViewChecked

only when genuinely required.

Need one-time work after DOM rendering?

Use:

Text
afterNextRender

Need work after every application render?

Use:

Text
afterEveryRender

Need cleanup?

Use:

Text
ngOnDestroy
DestroyRef
takeUntilDestroyed

depending on the scenario.

Practical Lifecycle Example: Dashboard Component

Consider a dashboard that:

  • receives a user ID from its parent
  • loads user information
  • creates a timer
  • initializes a chart after rendering
  • removes the timer when destroyed

A simplified implementation could look like:

TypeScript
import {
  afterNextRender,
  Component,
  DestroyRef,
  inject,
  Input,
  OnChanges,
  SimpleChanges
} from '@angular/core';

@Component({
  selector: 'app-dashboard',
  template: `
    <h2>{{ userName }}</h2>
    <div id="chart"></div>
  `
})
export class DashboardComponent implements OnChanges {

  @Input() userId = 0;

  userName = '';

  private destroyRef = inject(DestroyRef);

  constructor() {

    const timerId = setInterval(() => {
      console.log('Refreshing dashboard');
    }, 5000);

    this.destroyRef.onDestroy(() => {
      clearInterval(timerId);
    });

    afterNextRender(() => {
      console.log('Initialize chart here');
    });
  }

  ngOnChanges(changes: SimpleChanges): void {

    if (changes['userId']) {
      this.loadUser(this.userId);
    }
  }

  loadUser(id: number): void {
    console.log('Loading user:', id);
  }
}

This example demonstrates that lifecycle APIs should be selected according to responsibility:

Text
Input changes
→ ngOnChanges

Rendered DOM required
→ afterNextRender

Cleanup required
→ DestroyRef

Performance Considerations

Lifecycle hooks themselves are not automatically expensive.

Performance problems usually happen because developers place expensive code inside hooks that run frequently.

Be especially careful with:

Text
ngDoCheck
ngAfterContentChecked
ngAfterViewChecked
afterEveryRender

Avoid unnecessary:

  • loops
  • sorting
  • DOM measurements
  • network calls
  • JSON processing
  • complex calculations
  • repeated object creation

inside frequently executing callbacks.

A lifecycle method should have a clear reason to exist.

Do not implement lifecycle hooks merely because Angular provides them.

Lifecycle Best Practices

  1. Keep constructors focused on dependency injection and simple setup.
  1. Use ngOnInit for initialization that depends on initial component state.
  1. Use ngOnChanges when behavior must respond specifically to input changes.
  1. Avoid ngDoCheck unless custom change checking is genuinely required.
  1. Keep ngAfterContentChecked and ngAfterViewChecked lightweight.
  1. Use ngAfterViewInit when working with initialized view queries.
  1. Use afterNextRender for one-time work requiring the rendered DOM.
  1. Use afterEveryRender only when work genuinely needs to happen after every render.
  1. Separate DOM reads and writes with render phases when performance matters.
  1. Clean up timers, listeners, manually managed subscriptions, and third-party resources.
  1. Consider DestroyRef when keeping setup and cleanup logic together makes the code easier to understand.
  1. Consider takeUntilDestroyed() for Angular-aware RxJS subscription cleanup.
  1. Never perform repeated HTTP requests from frequently executing lifecycle hooks.
  1. Avoid unnecessary state modification during Angular's checking process.
  1. Implement only the hooks that the component actually needs.

Lifecycle Mental Model

Instead of memorizing every method independently, remember lifecycle responsibilities:

Text
CREATE
│
├── constructor
│
INITIALIZE DATA
│
├── ngOnChanges
├── ngOnInit
│
CHECK
│
├── ngDoCheck
│
CONTENT
│
├── ngAfterContentInit
├── ngAfterContentChecked
│
VIEW
│
├── ngAfterViewInit
├── ngAfterViewChecked
│
RENDERED DOM
│
├── afterNextRender
├── afterEveryRender
│
DESTROY
│
├── ngOnDestroy
└── DestroyRef cleanup

This model makes it easier to choose the correct API based on when your code needs to run, instead of trying to memorize lifecycle names without understanding their purpose.

Key Takeaways

Angular lifecycle APIs provide controlled points for running code during component creation, change checking, rendering, and destruction.

The most commonly useful lifecycle concepts are:

  • constructor for creating the class and injecting dependencies
  • ngOnInit for initial setup
  • ngOnChanges for reacting to input changes
  • ngAfterContentInit for projected content
  • ngAfterViewInit for initialized views
  • afterNextRender for one-time post-render DOM work
  • afterEveryRender for repeated post-render work
  • ngOnDestroy for final cleanup
  • DestroyRef for modern destruction callbacks
  • takeUntilDestroyed() for convenient RxJS cleanup

The key is not to use every lifecycle hook. Good Angular components use only the lifecycle APIs required by their actual behavior and keep frequently executed hooks as lightweight as possible.

Question Hint