A Beginner's Guide to Angular Lifecycle Hooks (With Code Examples)

Luis Ramirez Jr
Luis Ramirez Jr
hero image

When you’re building an Angular application, understanding the lifecycle of a component is crucial for building robust and maintainable code.

This is because each Angular component passes through stages of created, updated, and destroyed - just like living beings go through stages in life. These stages define how your app behaves, and Angular provides lifecycle hooks that give you control over what happens at each stage.

Don’t worry if that seems complex. In this guide, I’ll walk you through how these hooks work, showing you where and why they’re important in real-world development.

So whether you’re starting out with Angular or want to deepen your knowledge, by the end of this guide, you’ll know exactly how to manage your components effectively.

Sidenote: If you want to learn more about this topic, and dive deep into Angular, then check out my Complete Angular Developer course:

learn angular

Covering the latest Angular features, I guarantee that this is the most comprehensive online course on Angular. You’ll build real-world projects (including a massive video sharing application) step-by-step, and learn everything you need to get hired as an Angular Developer this year.

With that out of the way, let’s get into the guide…

Understanding the Angular lifecycle hooks sequence (with examples)

Angular’s lifecycle hooks allow you to manage specific moments in a component’s lifecycle, from its creation to its destruction.

In this section, we’ll walk through each of the lifecycle hooks in the order they’re triggered and provide detailed examples of when and why to use them.

ngOnChanges: Responding to input changes

Angular’s lifecycle hooks allow you to manage specific moments in a component’s lifecycle, from its creation to its destruction.

In this section, we’ll walk through each of the lifecycle hooks in the order they’re triggered and provide detailed examples of when and why to use them.

For example

Imagine you have a product detail component in an e-commerce app, where the parent component passes a product object as an input. If the user switches to a different product, you need the child component to immediately reflect the new product data.

The good news is that ngOnChanges lets you detect the change and take action:

export class ProductComponent implements OnChanges {
  @Input() product: any;

  ngOnChanges(changes: SimpleChanges) {
	if (changes['product']) {
  	console.log('Product changed to: ', changes['product'].currentValue);
  	this.updateProductDetails();
	}
  }

  updateProductDetails() {
	// Logic to update product details based on the new product input
  }
}

In this example, ngOnChanges detects changes to the product input and triggers logic to update the component accordingly.

When using ngOnChanges, it’s important to ensure that the logic inside this hook is efficient. If the logic becomes too complex, breaking it down into smaller methods can help keep your component manageable and avoid performance issues.

ngOnInit: Initializing your component

Once Angular sets the component’s inputs, it triggers ngOnInit. This hook is used for initialization tasks that only need to run once when the component is created, such as fetching data from an API or setting up initial configurations

For example

Let’s consider a dashboard component that loads user data when it’s first initialized. Here’s how ngOnInit helps you ensure data fetching occurs at the right moment:

export class DashboardComponent implements OnInit {
  userData: any;

  ngOnInit() {
	console.log('Component initialized!');
	this.fetchUserData();
  }

  fetchUserData() {
	// Logic to fetch user data from an API
  }
}

By placing the API call in ngOnInit, you ensure that the data fetching logic runs once, right after the component has been fully initialized. It’s a good idea to use ngOnInit for tasks such as fetching data, setting default values, or preparing resources.

Just be cautious not to put long-running operations here, as that could delay the component's rendering.

ngDoCheck: Custom change detection

Angular’s default change detection mechanism works well in most cases, but there are times when you need to manually check for changes that Angular might miss—such as when dealing with deeply nested objects.

This is where ngDoCheck comes in.

For example

Suppose you have a form with complex validation logic that involves deeply nested objects. You might need custom change detection to ensure the form is properly validated even when Angular doesn’t detect the changes automatically:

export class ComplexFormComponent implements DoCheck {
  formData: any;

  ngDoCheck() {
	console.log('Running custom change detection');
	this.checkFormData();
  }

  checkFormData() {
	// Custom logic to check and validate form data
  }
}

Here, ngDoCheck allows you to run custom logic to check for changes in the formData object and ensure everything is validated correctly.

Keep in mind that while ngDoCheck is powerful, you should use it carefully to avoid slowing down your application, especially if it’s overused for deep checking.

ngAfterContentInit and ngAfterContentChecked: Managing projected content

These hooks help manage projected content, which is content from a parent component inserted into a child component using <ng-content>.

  • ngAfterContentInit: Fires once, after the projected content has been inserted
  • ngAfterContentChecked: Fires after every change detection cycle for projected content

These hooks are especially useful when dealing with dynamic content that changes within the component after being inserted.

ngAfterViewInit and ngAfterViewChecked: DOM Interaction after initialization

ngAfterViewInit runs after your component’s view—including any child components—has been fully initialized.

This is typically used for DOM manipulation or for initializing third-party libraries that depend on DOM elements being available.

For example

If you’re using a third-party chart library that needs access to the DOM to render a chart, you’d initialize it in ngAfterViewInit to ensure the DOM elements are ready:

export class ChartComponent implements AfterViewInit, AfterViewChecked {
  @ViewChild('chartContainer') chartContainer: ElementRef;

  ngAfterViewInit() {
	console.log('View initialized!');
	this.initializeChart();
  }

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

  initializeChart() {
	// Logic to initialize a chart inside the DOM element
	this.chartContainer.nativeElement.style.width = '100%';
  }
}

In this case, ngAfterViewInit ensures that the chart library is only initialized once the DOM elements are in place, while ngAfterViewChecked allows you to monitor any subsequent changes in the view.

These hooks are useful for interacting with DOM elements or initializing third-party libraries that rely on those elements being fully rendered.

ngOnDestroy: Cleaning up resources

The final lifecycle hook, ngOnDestroy, is called just before Angular destroys your component. It’s used to clean up resources like event listeners, subscriptions, or timers that won’t be needed after the component is gone.

For example

If your component listens for real-time data through an observable, ngOnDestroy ensures that you unsubscribe from the observable to prevent memory leaks:

export class RealTimeDataComponent implements OnDestroy {
  private subscription: Subscription;

  ngOnInit() {
	this.subscription = this.dataService.getRealTimeUpdates().subscribe(data => {
  	// Handle real-time data
	});
  }

  ngOnDestroy() {
	console.log('Component destroyed!');
	this.subscription.unsubscribe();
  }
}

In this example, ngOnDestroy ensures that the observable subscription is properly cleaned up when the component is destroyed.

Always use ngOnDestroy to stop long-running processes like event listeners or subscriptions, which can continue running in the background and cause performance issues if not properly managed.

Advanced lifecycle hook techniques

While the lifecycle hooks covered above are sufficient for most common use cases, advanced hooks offer greater control when dealing with more complex scenarios, such as interacting with the DOM or managing dynamic UI updates.

So let's take a look at some more advanced options.

Interacting with the DOM using ngAfterViewInit and ngAfterViewChecked

When dealing with the view and its elements, ngAfterViewInit and ngAfterViewChecked are useful for more advanced scenarios that involve DOM manipulation after a view has been fully initialized.

ngAfterViewInit is ideal when initializing third-party libraries that depend on DOM elements being present, while ngAfterViewChecked is valuable for monitoring and optimizing view changes after every change detection cycle.

For example

export class MyComponent implements AfterViewInit, AfterViewChecked {
  @ViewChild('myElement') myElement: ElementRef;

  ngAfterViewInit() {
	console.log('View has been initialized!');
	this.myElement.nativeElement.style.backgroundColor = 'blue';
  }

  ngAfterViewChecked() {
	console.log('View has been checked!');
  }
}

In this scenario, ngAfterViewInit ensures that the DOM manipulation happens after the component is fully rendered, while ngAfterViewChecked helps monitor and react to changes in the view.

Managing child elements with ngOnContentChild and ngOnViewChild

If your component relies on dynamically changing child elements, Angular provides two specific hooks: ngOnContentChild and ngOnViewChild.

These hooks allow you to detect and react to changes in content or view child components.

For example

export class MyComponent implements OnChanges {
  @ContentChild(ChildComponent) childComponent: ChildComponent;
  @ViewChild(ViewChildComponent) viewChild: ViewChildComponent;

  ngOnChanges() {
	console.log('Changes detected in Content or View Child!');
  }
}

This hook helps ensure you can monitor and respond to changes in both content and view children, making it useful for managing dynamic UI elements or complex parent-child relationships.

Synchronizing the DOM with afterRender and afterNextRender

Angular has introduced two additional lifecycle functions as part of its signals system: afterRender and afterNextRender.

These provide fine-grained control over DOM updates and rendering cycles.

  • afterNextRender: Best used for tasks that should run once after the DOM is rendered, such as initializing a third-party library
  • afterRender: Ideal for scenarios where continuous DOM updates are necessary, such as handling real-time updates or frequent resizing

Example of afterNextRender

import { afterNextRender } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<div></div>',
})
export class AppComponent {
  constructor() {
	afterNextRender(() => {
  	console.log('Runs only once after the next render cycle');
  	this.initializeLibrary();
	});
  }

  initializeLibrary() {
	// Initialize third-party library that depends on DOM elements
  }
}

Example of afterRender:

import { afterRender } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<div></div>',
})
export class AppComponent {
  constructor() {
	afterRender(() => {
  	console.log('Runs after every render cycle');
  	this adjustLayout();
	});
  }

  adjustLayout() {
	// Adjust layout based on DOM changes or user interaction
  }
}

Best practices for more advanced lifecycle hooks

#1. Use with care

While advanced hooks such as ngAfterViewInit, and ngAfterViewChecked can offer greater control over your components, they can also add significant complexity.

Overusing these hooks can lead to tangled, hard-to-maintain code. If in doubt, always prioritize simplicity over adding complexity.

#2. Optimize for performance

Because many advanced hooks are triggered multiple times during the component lifecycle (e.g., after every change detection cycle), inefficient logic within these hooks can lead to performance issues, especially in larger applications.

Hooks like ngAfterViewChecked are called frequently, so even small inefficiencies can significantly slow down your app.

For example

export class MyComponent implements AfterViewChecked {
  viewUpdated = false;

  ngAfterViewChecked() {
	if (!this viewUpdated) {
  	this updateView();
  	this.viewUpdated = true;
	}
  }

  updateView() {
	// Perform DOM updates
  }
}

#3. Prioritize readability

As your Angular components become more complex, avoid overloading hooks with too much logic. Break down complex logic into smaller, well-named methods and keep your component clean and easy to read.

For example

export class MyComponent implements AfterContentChecked {
  ngAfterContentChecked() {
	this.performViewChecks();
  }

  performViewChecks() {
	this checkUserData();
	this updateContentDisplay();
  }

  checkUserData() {
	// Logic to validate or check user data
  }

  updateContentDisplay() {
	// Logic to update the content display
  }
}

#4. Avoid side effects in hooks

Lifecycle hooks should be used to manage a component's internal state, but they should not introduce significant side effects that impact the broader application.

Unintended side effects, such as triggering additional change detection cycles or performing excessive DOM manipulations, can destabilize the application.

Walkthrough example: How to build a component with multiple lifecycle hooks

Alright, time to put all this together.

Let’s walk through a practical example of building a user profile component that displays user information and allows for profile editing. This component will need to handle several tasks:

  • Fetching user data when it’s initialized
  • Monitoring changes to the user’s inputs
  • Managing dynamic content such as updating the user’s profile picture, and
  • Cleaning up resources when the component is destroyed

Here’s how you can implement this using Angular lifecycle hooks.

Step #1. Setting up our component

We’ll begin by setting up the component and using the ngOnInit hook to fetch the user’s data as soon as the component is initialized. This is a common use case for ngOnInit, where you need to retrieve data when the component first loads.

Here’s how you can implement this:

import { Component, OnInit, OnChanges, DoCheck, OnDestroy } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app-user-profile',
  templateUrl: './user-profile.component.html',
})
export class UserProfileComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
  userData: any;

  constructor(private userService: UserService) {}

  ngOnInit() {
	console.log('Component initialized!');
	this.userService.getUserData().subscribe(data => {
  	this.userData = data;
	});
  }

  ngOnChanges() {
	console.log('Input properties changed!');
  }

  ngDoCheck() {
	console.log('Custom change detection running!');
  }

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

In this example, ngOnInit is the starting point for fetching user data. The component calls an API to retrieve the necessary user details and populate the UI.

As the data changes, the ngOnChanges and ngDoCheck hooks come into play. ngOnChanges detects changes in the component's input properties, while ngDoCheck handles any custom change detection logic that Angular’s default mechanism might not catch.

Finally, when the component is destroyed - such as when the user navigates away from the profile page—the ngOnDestroy hook is triggered. This ensures that any active subscriptions, like the one used for real-time data, are properly cleaned up to prevent memory leaks.

Step #2. Handling content projection with ngAfterContentInit and ngAfterContentChecked

To manage dynamic content projected from a parent component, like additional user details or custom sections, we use ngAfterContentInit to initialize the content after it's been projected, and ngAfterContentChecked to monitor changes.

For example

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

@Component({
  selector: 'app-user-profile',
  templateUrl: './user-profile.component.html',
})
export class UserProfileComponent implements AfterViewInit, AfterViewChecked {
  @ViewChild('profilePicture') profilePicture: ElementRef;

  ngAfterViewInit() {
	console.log('View has been initialized!');
	this.profilePicture.nativeElement.src = this.userData.profilePictureUrl;
  }

  ngAfterViewChecked() {
	console.log('View has been checked!');
  }
}

In this example, ngAfterContentInit ensures the projected ButtonComponent is properly initialized, while ngAfterContentChecked monitors changes in the content, ensuring any updates are correctly handled.

Step #3. Cleaning up our resources with ngOnDestroy

It’s essential to clean up any resources your component is using when the component is no longer needed.

For example if the component subscribes to real-time data updates, you need to then unsubscribe from these observables to avoid memory leaks. The ngOnDestroy hook is your go-to for managing this.

Here’s how you can ensure the component properly cleans up subscriptions:

export class UserProfileComponent implements OnDestroy {
  private subscription: Subscription;

  ngOnInit() {
	this.subscription = this.userService.getUserData().subscribe(data => {
  	this.userData = data;
	});
  }

  ngOnDestroy() {
	console.log('Component destroyed!');
	this.subscription.unsubscribe();
  }
}

In this case, ngOnDestroy ensures that the subscription to the getUserData observable is canceled when the component is destroyed. This is crucial for preventing memory leaks, especially in larger applications where components are frequently created and destroyed.

What’s next?

Phew, that was a lot to cover! Hopefully, you managed to stick with me and understand everything we covered.

Lifecycle hooks are a powerful part of Angular that give you the ability to control how your components behave during their lifecycle, and by understanding when and why to use each hook, you can build Angular applications that are efficient, maintainable, and responsive.

Whether you’re managing input changes with ngOnChanges, fetching data in ngOnInit, or cleaning up resources with ngOnDestroy, each hook plays a crucial role in keeping your application running smoothly. By mastering these hooks, you can handle any scenario your Angular application throws at you and ensure that your components behave exactly the way you need them to.

Now you just need to add these to your own projects and play around with them—it’s the fastest way to get to grips with new concepts. So have at it!

P.S.

Remember, if you struggle with any of the concepts in this guide, or want to take a deep dive into Angular, check out my complete Angular Developer course:

learn angular

This is the only Angular course you need to learn Angular, build enterprise-level applications from scratch, and get hired as an Angular Developer.

Plus, once you join, you'll have the opportunity to ask questions in our private Discord community from me, other students and working professionals.


Not only that, but as a member - you’ll also get access to every course in the Zero To Mastery library!

More from Zero To Mastery

Top 10 Angular Projects For Beginners And Beyond preview
Popular
Top 10 Angular Projects For Beginners And Beyond

Are you looking to skill up in Angular? We give you 10 Angular practice projects to make that happen plus our Top 3 you can't miss for you to up your game!

Beginners Guide To Angular ngOnChanges (With Code Examples) preview
Beginners Guide To Angular ngOnChanges (With Code Examples)

Discover how to use Angular's ngOnChanges lifecycle hook. Learn its benefits, use cases, + how it compares to other hooks to enhance your app's responsiveness.

Mastering Angular Modals and Popups: A Step-by-Step Beginner’s Guide preview
Mastering Angular Modals and Popups: A Step-by-Step Beginner’s Guide

Learn how to build custom and Angular Material modals and popups. This beginner-friendly guide walks you through every step to enhance your Angular projects.