Optimize Angular Performance: Lazy Loading & Change Detection

By | October 24, 2025

Introduction

Angular applications can suffer from performance issues as they grow in complexity. Two critical techniques for optimizing Angular applications are Lazy Loading of modules and strategic management of Change Detection. This tutorial provides an in-depth exploration of these concepts with practical implementations.

Learning Objectives: – Master lazy loading architecture – Implement optimized change detection strategies – Improve application load time and runtime performance – Advanced debugging and optimization techniques

Prerequisites: – Basic Angular understanding (Components, Modules, Services) – TypeScript proficiency – Node.js (v16+) and npm installed

Tools & Technologies: – Angular CLI (v15+) – Chrome DevTools – Webpack Bundle Analyzer

Estimated Completion Time: 90 minutes


Fundamentals and Core Concepts

Key Terminology

  1. Lazy Loading: Loading feature modules on-demand
  2. Change Detection: Angular's mechanism to update DOM
  3. NgModule: Logical boundary for application features
  4. Route Configuration: Defines navigation paths

Change Detection Explained

Angular uses a tree-based change detection system. The default strategy (CheckAlways) verifies all components when any change occurs. The OnPush strategy only checks when: – Input references change – Event handlers trigger changes – Explicit detection is requested

Lazy Loading Architecture:

App Module ├── Core Module (Eager) ├── Shared Module (Eager) └── Route Configuration ├── /dashboard (Lazy) ├── /reports (Lazy) └── /admin (Lazy)



Prerequisites and Environment Setup

Installation Guide

  1. Install Angular CLI globally:
npm install -g @angular/cli


  1. Create new application (Skip if modifying existing):
ng new perf-app –strict –routing –style=scss && cd perf-app


Verify Environment

ng version


Expected output:

Angular CLI: 15.2.4 Node: 16.14.2 Package Manager: npm 8.5.0



Step-by-Step Implementation

1. Lazy Loading Setup

Basic Implementation (products.module.ts):

// app-routing.module.ts const routes: Routes = [ { path: ‘products’, loadChildren: () => import(‘./products/products.module’) .then(m => m.ProductsModule) } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule {}


Advanced Preloading Strategy:

// custom-preloading.service.ts @Injectable({ providedIn: ‘root’ }) export class SelectivePreloadingService implements PreloadingStrategy { preload(route: Route, load: () => Observable): Observable { return route.data?.preload ? load() : of(null); } } // Apply in app-routing.module.ts RouterModule.forRoot(routes, { preloadingStrategy: SelectivePreloadingService })


2. Change Detection Optimization

OnPush Strategy Implementation:

// products-list.component.ts @Component({ selector: ‘app-products-list’, templateUrl: ‘./products-list.component.html’, changeDetection: ChangeDetectionStrategy.OnPush }) export class ProductsListComponent { @Input() products: Product[]; constructor(private cdr: ChangeDetectorRef) {} refreshData() { // Optional manual trigger this.cdr.detectChanges(); } }


Immutable Data Pattern:

updateProduct(updatedProduct: Product) { this.products = this.products.map(p => p.id === updatedProduct.id ? { …p, …updatedProduct } : p ); }



Practical Examples and Use Cases

E-commerce Scenario

Lazy Loading Structure:

const routes: Routes = [ { path: ‘checkout’, loadChildren: () => import(‘./checkout/checkout.module’) .then(m => m.CheckoutModule), data: { preload: true } // High-priority route }, { path: ‘product/:id’, loadChildren: () => import(‘./product-details/product-details.module’) .then(m => m.ProductDetailsModule) } ];


Dashboard Widget (OnPush Example):

@Component({ selector: ‘sales-widget’, template: `
{{ salesData | json }}
`, changeDetection: ChangeDetectionStrategy.OnPush }) export class SalesWidget { @Input() salesData: SalesData; // Only updates when salesData reference changes }



Advanced Techniques and Optimization

Bundle Analysis and Optimization

  1. Install analyzer:
npm install webpack-bundle-analyzer –save-dev


  1. Generate build stats:
ng build –stats-json


  1. Analyze bundles:
npx webpack-bundle-analyzer dist/perf-app/stats.json


Common Optimizations: - Disable Ivy for legacy projects - Implement module federation - Configure differential loading


Testing, Debugging, and Troubleshooting

Common Errors and Fixes

Lazy Loading Issue:

Error: Cannot find module ‘./products/products.module’


Solution:

// Ensure correct relative path: loadChildren: () => import(‘./features/products/products.module’)


Change Detection Problem:

Component not updating with OnPush


Verification Steps: 1. Check input reference changes 2. Verify manual change detection calls 3. Ensure async pipe is properly used

Debugging Tools

  1. Angular DevTools: Component inspection
  2. Chrome Performance Tab: Profile change detection cycles
  3. Router Tracing:
RouterModule.forRoot(routes, { enableTracing: true })



Conclusion and Next Steps

Implementing lazy loading and optimized change detection can dramatically improve Angular application performance: - Reduce initial bundle size by 30-60% - Lower memory consumption - Improve runtime efficiency

Recommended Next Steps: 1. Implement Angular Universal (SSR) 2. Explore Zone.js optimization 3. Investigate Component Store patterns

Learning Resources: - Angular Performance Checklist - RxJS Optimization Techniques


> Key Insight: The most effective performance gains often come from architecture decisions made early in development. Strategically combine lazy loading with OnPush change detection for maximum impact.

Leave a Reply