Angular 20 Lifecycle Explained: From Application Bootstrap to Component Destruction
When developers talk about the Angular lifecycle, the first things that usually come to mind are:
ngOnInit()ngOnChanges()ngAfterViewInit()ngOnDestroy()
But these are only part of the story.
Have you ever wondered what actually happens before ngOnInit() executes?
How does Angular bootstrap an application? When are dependency injection providers initialized? How does the router activate a component? What triggers change detection? And what happens when the entire application is destroyed?
To understand Angular properly, we need to look at two different lifecycle levels:
- Application Lifecycle – The lifecycle of the entire Angular application.
- Component Lifecycle – The lifecycle of individual component instances.
Let's break down both using Angular 20.
1. Understanding the Angular Architecture
Before exploring the lifecycle, let's understand the high-level execution flow.
Browser
|
v
index.html
|
v
main.ts
|
v
bootstrapApplication()
|
v
Application Configuration
|
v
Platform & Dependency Injection
|
v
Root Component Creation
|
v
Initial Rendering
|
v
Router Activation
|
v
Application Runtime
|
v
Change Detection & Rendering
|
v
Application Teardown
This is a conceptual application execution flow, not a fixed sequence of Angular lifecycle hooks.
2. Angular Application Lifecycle (Application Level)
The application lifecycle describes how Angular initializes, runs, and eventually destroys the application.
Phase 1: Application Bootstrap
The browser loads index.html, which references the compiled JavaScript bundles.
Angular starts executing the application's entry point, typically main.ts.
Example:
main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig)
.catch((error) => console.error(error));
What happens here?
- Angular starts the application bootstrap process.
- The browser platform is created or reused.
- Angular creates the application-level injector.
- Application providers are configured.
- The root component is instantiated.
- Angular initializes and renders the root component.
Phase 2: Application Configuration and Dependency Injection
Angular uses dependency injection to manage services and application-wide dependencies.
app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient()
]
};
Common application-level providers include:
- HTTP Client
- Router
- Authentication services
- Logging services
- Global configuration
- Interceptors
- Application-wide state management
Angular also supports different injector scopes:
| Injector | Responsibility |
|---|---|
| Platform Injector | Platform-level dependencies |
| Environment Injector | Application and environment providers |
| Route Injector | Route-specific providers |
| Element Injector | Component and directive-level providers |
Understanding injector scopes is important when managing service instances in large applications.
Phase 3: Root Component Initialization
After bootstrap, Angular creates the root component.
For example:
@Component({
selector: 'app-root',
standalone: true,
template: `
<app-header />
<router-outlet />
<app-footer />
`
})
export class AppComponent {
constructor() {
console.log('Root component created');
}
}
Angular begins the component lifecycle and renders the application's initial view.
Phase 4: Application Runtime
Once the application is running, Angular responds to different events:
- User interactions
- Router navigation
- HTTP responses
- Signal updates
- Component input changes
- State updates
- Change detection notifications
The router activates and deactivates routed components as navigation changes.
For example:
export const routes: Routes = [
{
path: 'dashboard',
loadComponent: () =>
import('./dashboard/dashboard.component')
.then(m => m.DashboardComponent)
},
{
path: 'profile',
loadComponent: () =>
import('./profile/profile.component')
.then(m => m.ProfileComponent)
}
];
When the user navigates to /dashboard, Angular activates the dashboard component.
When navigating away, Angular normally destroys the routed component unless route reuse or another mechanism retains it.
Phase 5: Application Teardown
Application teardown occurs when the Angular application itself is explicitly destroyed.
For example:
const appRef = await bootstrapApplication(
AppComponent,
appConfig
);
// Later, when the application must be destroyed
appRef.destroy();
Angular destroys the application and its associated views and resources.
Important: Navigating away from a component does not mean the entire Angular application is destroyed.
This distinction matters in single-page applications.
3. Angular Component Lifecycle (Component Level)
Now let's look at the component lifecycle.
Every component instance created by Angular has its own lifecycle.
Angular provides lifecycle hooks that allow developers to execute logic at specific stages.
We can divide the lifecycle into five phases.
Phase 1: Creation Phase
1. constructor()
The constructor executes when JavaScript creates the component instance.
export class DashboardComponent {
constructor() {
console.log('Constructor executed');
}
}
Use it for:
- Dependency injection
- Basic instance initialization
- Registering destruction callbacks
Avoid:
- Heavy business logic
- Complex API initialization
- Direct DOM manipulation
2. ngOnChanges()
Called when Angular detects changes to data-bound inputs.
export class UserComponent implements OnChanges {
@Input() userId!: number;
ngOnChanges(changes: SimpleChanges): void {
console.log(changes);
}
}
Important points:
- Called before
ngOnInit()when initial inputs are present. - Called whenever an input value changes.
- Receives previous and current values through
SimpleChanges.
3. ngOnInit()
Called once after Angular initializes the component's input properties.
export class DashboardComponent implements OnInit {
ngOnInit(): void {
console.log('Component initialized');
this.loadDashboard();
}
loadDashboard(): void {
// Load initial dashboard data
}
}
Typical use cases:
- Initial API requests
- Loading component data
- Initializing component-specific state
- Setting up application logic
Phase 2: Content Projection Phase
This phase is relevant when using projected content through <ng-content>.
4. ngDoCheck()
Called whenever Angular checks the component for changes.
ngDoCheck(): void {
console.log('Custom change detection check');
}
Use it only when you need custom change-detection logic.
It can execute frequently and negatively affect performance.
5. ngAfterContentInit()
Called once after Angular initializes the projected content.
ngAfterContentInit(): void {
console.log('Projected content initialized');
}
6. ngAfterContentChecked()
Called after Angular checks the projected content.
ngAfterContentChecked(): void {
console.log('Projected content checked');
}
Avoid expensive operations here because this hook can execute frequently.
Phase 3: View Initialization Phase
This phase deals with the component's own template and child views.
7. ngAfterViewInit()
Called once after Angular initializes the component's view and child views.
ngAfterViewInit(): void {
console.log('Component view initialized');
}
Typical use cases:
- Accessing
ViewChild - Accessing
ViewChildren - Initializing integrations that require an initialized view
8. ngAfterViewChecked()
Called after Angular checks the component's view and child views.
ngAfterViewChecked(): void {
console.log('Component view checked');
}
Avoid performing expensive operations or repeatedly modifying state here.
Phase 4: Change Detection Phase
After initialization, Angular continues checking components when change detection is scheduled.
Common triggers include:
- Template event handlers
- Input changes
- Signal updates
- AsyncPipe notifications
- Explicit change-detection notifications
During checks, Angular can execute:
-
ngOnChanges()when inputs change ngDoCheck()ngAfterContentChecked()ngAfterViewChecked()
These hooks do not all execute on every event. Their execution depends on the relevant component and lifecycle conditions.
Phase 5: Destruction Phase
9. ngOnDestroy()
Called once just before Angular destroys a component.
export class DashboardComponent implements OnDestroy {
ngOnDestroy(): void {
console.log('Dashboard component destroyed');
}
}
Typical use cases:
- Unsubscribe from manually managed subscriptions
- Clear timers
- Remove event listeners
- Release external resources
- Clean up third-party integrations
A modern alternative is DestroyRef.
import { Component, DestroyRef, inject } from '@angular/core';
@Component({
selector: 'app-dashboard',
standalone: true,
template: `<h2>Dashboard</h2>`
})
export class DashboardComponent {
private destroyRef = inject(DestroyRef);
constructor() {
this.destroyRef.onDestroy(() => {
console.log('Dashboard cleanup executed');
});
}
}
You can also use takeUntilDestroyed() for RxJS subscriptions.
import { Component, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export class DashboardComponent {
private destroyRef = inject(DestroyRef);
constructor(private dashboardService: DashboardService) {
this.dashboardService.getDashboardData()
.pipe(
takeUntilDestroyed(this.destroyRef)
)
.subscribe(data => {
console.log(data);
});
}
}
This reduces manual subscription cleanup.
4. Complete Component Lifecycle Execution Order
For a component's initial initialization, the usual lifecycle-hook sequence is:
constructor()
|
v
ngOnChanges() (if initial inputs are present)
|
v
ngOnInit()
|
v
ngDoCheck()
|
v
ngAfterContentInit()
|
v
ngAfterContentChecked()
|
v
ngAfterViewInit()
|
v
ngAfterViewChecked()
|
v
Subsequent Change Detection
|
v
ngOnChanges() (when inputs change)
ngDoCheck()
ngAfterContentChecked()
ngAfterViewChecked()
|
v
ngOnDestroy()
Remember:
-
ngOnInit()executes once. -
ngAfterViewInit()executes once. -
ngAfterContentInit()executes once. - Checked hooks can execute many times.
-
ngOnChanges()executes when Angular detects input changes. -
ngOnDestroy()executes once before component destruction.
The exact order of checks across parent and child components depends on Angular's traversal of the component tree.
5. Additional Angular 20 Rendering APIs
Angular also provides two important rendering callbacks.
These are not traditional class-based component lifecycle hooks.
afterNextRender()
Executes a callback after Angular's next application render.
import { Component, afterNextRender } from '@angular/core';
@Component({
selector: 'app-dashboard',
standalone: true,
template: `<h2>Dashboard</h2>`
})
export class DashboardComponent {
constructor() {
afterNextRender(() => {
console.log('Next render completed');
});
}
}
afterEveryRender()
Executes after every Angular application render.
import { Component, afterEveryRender } from '@angular/core';
@Component({
selector: 'app-dashboard',
standalone: true,
template: `<h2>Dashboard</h2>`
})
export class DashboardComponent {
constructor() {
afterEveryRender(() => {
console.log('Application render completed');
});
}
}
These APIs are useful for DOM-related work that needs to happen after rendering.
They also support phased DOM operations to help avoid unnecessary layout thrashing.
Note that render callbacks do not run during server-side rendering or build-time prerendering.
6. Angular 20: Zoneless Change Detection
One important Angular 20 capability is optional zoneless change detection.
Traditionally, Angular applications commonly use Zone.js to help identify asynchronous activity and schedule change detection.
Angular 20 allows applications to enable zoneless change detection.
Example:
import { ApplicationConfig } from '@angular/core';
import { provideZonelessChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
provideZonelessChangeDetection()
]
};
With zoneless change detection, Angular relies on explicit notifications such as:
- Updating a signal read by a template
- Calling
ChangeDetectorRef.markForCheck() - Updating component inputs through Angular APIs
- Template event handlers
- AsyncPipe notifications
This can reduce unnecessary change-detection scheduling.
Important: Zoneless change detection is optional in Angular 20. It became the default in Angular 21 and later.
7. Best Practices for Enterprise Angular Applications
For large-scale Angular applications, understanding lifecycle execution is not enough. We also need to use it correctly.
| Scenario | Recommended approach |
|---|---|
| Dependency injection | Constructor or inject()
|
| Initial component data | ngOnInit() |
| React to input changes | ngOnChanges() |
| Access projected content | ngAfterContentInit() |
| Access child views | ngAfterViewInit() |
| DOM operations after rendering | afterNextRender() |
| Reactive state management | Signals |
| RxJS subscription cleanup | takeUntilDestroyed() |
| General resource cleanup | DestroyRef |
| Custom change detection | Avoid unless necessary |
Common mistakes to avoid
- Calling API services repeatedly inside
ngDoCheck(). - Performing expensive operations inside
ngAfterViewChecked(). - Modifying already-checked template state inside content or view checked hooks.
- Forgetting to clean up manually managed subscriptions.
- Confusing component destruction with application destruction.
- Using
setTimeout()as a workaround for lifecycle timing issues. - Assuming every asynchronous operation automatically triggers change detection in zoneless applications.
8. Final Thoughts
Understanding Angular lifecycle execution is essential when building scalable enterprise applications.
The most important distinction is:
Application Lifecycle = How the entire Angular application starts, runs, and shuts down.
Component Lifecycle = How individual components are created, initialized, checked, rendered, and destroyed.
When developers understand both levels, debugging becomes easier, resource management improves, and application architecture becomes more predictable.
If you are working with Angular 18, 19, or 20, these concepts are especially useful when upgrading applications, implementing standalone components, managing signals, and optimizing rendering performance.
Official Angular References
- Angular Component Lifecycle
- Angular Dependency Injection
- Angular Zoneless Change Detection
- Angular Dependency Injection Providers
What is your preferred approach for managing component cleanup in enterprise Angular applications — ngOnDestroy(), DestroyRef, or takeUntilDestroyed()? Share your experience in the comments.

Top comments (0)