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:

  1. Application Lifecycle – The lifecycle of the entire Angular application.
  2. 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

  1. Calling API services repeatedly inside ngDoCheck().
  2. Performing expensive operations inside ngAfterViewChecked().
  3. Modifying already-checked template state inside content or view checked hooks.
  4. Forgetting to clean up manually managed subscriptions.
  5. Confusing component destruction with application destruction.
  6. Using setTimeout() as a workaround for lifecycle timing issues.
  7. 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

What is your preferred approach for managing component cleanup in enterprise Angular applications — ngOnDestroy(), DestroyRef, or takeUntilDestroyed()? Share your experience in the comments.

Angular #Angular20 #FrontendDevelopment #TypeScript #WebDevelopment