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:
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…
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 changesAngular’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 componentOnce 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 detectionAngular’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 contentThese 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 insertedngAfterContentChecked
: Fires after every change detection cycle for projected contentThese hooks are especially useful when dealing with dynamic content that changes within the component after being inserted.
ngAfterViewInit
and ngAfterViewChecked
: DOM Interaction after initializationngAfterViewInit
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 resourcesThe 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.
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.
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.
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.
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 libraryafterRender
: Ideal for scenarios where continuous DOM updates are necessary, such as handling real-time updates or frequent resizingafterNextRender
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
}
}
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
}
}
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.
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
}
}
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
}
}
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.
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:
Here’s how you can implement this using Angular lifecycle hooks.
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.
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.
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.
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!
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:
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!