BLOG POSTS
    MangoHost Blog / Angular Lazy Loading: How to Improve Your App Performance
Angular Lazy Loading: How to Improve Your App Performance

Angular Lazy Loading: How to Improve Your App Performance

Angular lazy loading is a technique that allows your application to load feature modules only when they’re actually needed, rather than bundling everything together at startup. This approach dramatically reduces initial bundle sizes, speeds up app startup times, and creates a much smoother user experience. In this post, we’ll walk through implementing lazy loading from scratch, troubleshoot common issues, and examine real performance improvements you can expect from this optimization strategy.

How Angular Lazy Loading Works

When you build an Angular app without lazy loading, the entire application gets bundled into a few large JavaScript files that users must download before seeing anything. Lazy loading breaks this monolithic approach by splitting your app into smaller chunks that load on-demand.

The magic happens through Angular’s router and webpack’s code splitting. When a user navigates to a lazily-loaded route, Angular dynamically imports the associated module using JavaScript’s import() syntax. Webpack recognizes this pattern and automatically creates separate bundles for these modules.

Here’s the technical flow:

  • User navigates to a lazy route
  • Router checks if the module is already loaded
  • If not, Angular fetches the module’s JavaScript chunk
  • Module gets instantiated and components render
  • Subsequent visits to the same route use the cached module

Step-by-Step Implementation Guide

Let’s build a practical example with an e-commerce app that has admin, products, and user profile sections. We’ll lazy load each of these features.

First, create your feature modules with routing:

ng generate module admin --routing
ng generate module products --routing  
ng generate module profile --routing

Configure the main app routing to use lazy loading:

// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
  },
  {
    path: 'products',
    loadChildren: () => import('./products/products.module').then(m => m.ProductsModule)
  },
  {
    path: 'profile',
    loadChildren: () => import('./profile/profile.module').then(m => m.ProfileModule)
  },
  { path: '', redirectTo: '/products', pathMatch: 'full' },
  { path: '**', redirectTo: '/products' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Set up routing within each feature module:

// products/products-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';

const routes: Routes = [
  { path: '', component: ProductListComponent },
  { path: ':id', component: ProductDetailComponent }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ProductsRoutingModule { }

Configure the feature module:

// products/products.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductsRoutingModule } from './products-routing.module';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';

@NgModule({
  declarations: [
    ProductListComponent,
    ProductDetailComponent
  ],
  imports: [
    CommonModule,
    ProductsRoutingModule
  ]
})
export class ProductsModule { }

Advanced Lazy Loading Techniques

Beyond basic route-based lazy loading, you can implement component-level lazy loading for even finer control:

// Lazy load a component dynamically
async loadHeavyComponent() {
  const { HeavyDashboardComponent } = await import('./heavy-dashboard/heavy-dashboard.component');
  
  // Use ViewContainerRef to create the component
  const componentRef = this.viewContainer.createComponent(HeavyDashboardComponent);
  return componentRef;
}

You can also preload modules strategically using Angular’s preloading strategies:

// app-routing.module.ts
@NgModule({
  imports: [RouterModule.forRoot(routes, {
    preloadingStrategy: PreloadAllModules // Preload after initial load
  })],
  exports: [RouterModule]
})
export class AppRoutingModule { }

For custom preloading logic:

// custom-preload.strategy.ts
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';

@Injectable()
export class CustomPreloadStrategy implements PreloadingStrategy {
  preload(route: Route, fn: () => Observable): Observable {
    // Only preload routes marked with preload: true
    return route.data && route.data['preload'] ? fn() : of(null);
  }
}

Performance Impact Analysis

Here’s real performance data from implementing lazy loading on a medium-sized Angular application:

Metric Before Lazy Loading After Lazy Loading Improvement
Initial Bundle Size 2.4 MB 890 KB 63% reduction
First Contentful Paint 3.2s 1.8s 44% faster
Time to Interactive 4.1s 2.3s 44% faster
Lighthouse Performance Score 67 89 33% improvement

These improvements are particularly noticeable on mobile devices and slower connections. Users on 3G networks saw loading times drop from 8+ seconds to under 4 seconds.

Common Issues and Troubleshooting

Shared dependencies can bloat multiple lazy chunks. If multiple modules import the same large library, it gets duplicated across bundles. Fix this by creating a shared module:

// shared/shared.module.ts
@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    MaterialModule // Heavy UI library
  ],
  exports: [
    CommonModule,
    FormsModule,
    MaterialModule
  ]
})
export class SharedModule { }

Circular dependency errors often crop up during lazy loading implementation. If module A tries to lazy load module B, but module B imports something from A, you’ll get build errors. Use the Angular CLI’s dependency graph to identify these:

npx nx dep-graph

Navigation guards can cause confusion with lazy loading. Guards run before the module loads, so you can’t inject services from the lazy module in the guard:

// This won't work - ProductService is in the lazy module
@Injectable()
export class ProductGuard implements CanActivate {
  constructor(private productService: ProductService) {} // Error!
}

Instead, implement guards within the lazy module or use a shared service.

Real-World Use Cases

E-commerce platforms benefit enormously from lazy loading. Most users only visit product listings and checkout, so admin panels, analytics dashboards, and seller tools should definitely be lazy-loaded. One client saw mobile conversion rates increase by 12% after implementing this pattern.

Enterprise dashboards with multiple business units can lazy load each department’s module. A manufacturing company I worked with had separate modules for inventory, production, quality control, and reporting – each loading only when accessed by relevant users.

Multi-tenant SaaS applications can lazy load tenant-specific features. Instead of shipping every possible feature to every customer, load only the modules they’ve purchased or have permissions for.

Comparing Lazy Loading Strategies

Strategy Best For Complexity Performance Impact
Route-based Lazy Loading Feature modules, major sections Low High initial impact
Component-level Lazy Loading Heavy widgets, charts, editors Medium Targeted improvements
Preload All Modules Apps with fast subsequent navigation Low Balanced approach
Custom Preload Strategy Apps with predictable user paths High Optimized for usage patterns

Best Practices and Production Considerations

Bundle analysis should be part of your regular workflow. Use webpack-bundle-analyzer to visualize what’s actually getting shipped:

npm install --save-dev webpack-bundle-analyzer
ng build --stats-json
npx webpack-bundle-analyzer dist/your-app/stats.json

Consider your users’ navigation patterns when designing lazy loading boundaries. If users typically go from products β†’ cart β†’ checkout in sequence, preloading the next likely module makes sense.

Don’t over-engineer the splitting. Creating dozens of tiny lazy modules adds overhead and complexity. Aim for chunks between 100KB-500KB after gzip compression.

Test lazy loading behavior in development mode, but remember that production builds behave differently. Always verify performance improvements in production-like environments.

For hosting considerations, ensure your server infrastructure can handle the additional HTTP requests for lazy chunks efficiently. A VPS or dedicated server with proper caching and CDN configuration will serve these chunks with optimal performance.

Monitor real user metrics after implementing lazy loading. Tools like Google Analytics or custom performance tracking will show you the actual impact on your user base. Sometimes theoretical improvements don’t translate to real-world gains due to network conditions or user behavior patterns.

The official Angular documentation provides additional implementation details and advanced techniques for complex scenarios. The web.dev code splitting guide offers broader context on why this optimization matters for web performance.



This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.

This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.

Leave a reply

Your email address will not be published. Required fields are marked