Back to Blog

Angular Performance Optimization

Advanced techniques and strategies to build lightning-fast Angular applications

Introduction to Angular Performance

Performance is crucial for modern web applications. Users expect fast, responsive interfaces, and even a few seconds of delay can significantly impact user experience and conversion rates. Angular provides numerous tools and techniques to optimize application performance, from initial loading to runtime efficiency.

In this comprehensive guide, we'll explore advanced performance optimization strategies that can help you build Angular applications that load faster, run smoother, and provide exceptional user experiences. We'll cover everything from bundle optimization to change detection strategies.

Performance Impact Statistics

53%
Users abandon sites that take over 3 seconds to load
100ms
Delay can reduce conversions by 7%

Bundle Size Optimization

1. Tree Shaking and Dead Code Elimination

Angular CLI uses Webpack to automatically remove unused code. Ensure you're importing only what you need:


// ❌ Bad: Imports entire library
import * as _ from 'lodash';

// ✅ Good: Import only specific functions
import { debounce, throttle } from 'lodash';

// ✅ Even better: Use lodash-es for better tree shaking
import debounce from 'lodash-es/debounce';
import throttle from 'lodash-es/throttle';

2. Lazy Loading Modules

Implement lazy loading to split your application into smaller chunks that load on demand:


// app-routing.module.ts
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)
  }
];

// For standalone components (Angular 14+)
const routes: Routes = [
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard/dashboard.component')
      .then(m => m.DashboardComponent)
  }
];

3. Preloading Strategies

Configure intelligent preloading to balance initial load time with user experience:


// Custom preloading strategy
@Injectable()
export class CustomPreloadingStrategy implements PreloadingStrategy {
  preload(route: Route, load: () => Observable<any>): Observable<any> {
    // Preload only routes marked with data.preload = true
    return route.data && route.data['preload'] ? load() : of(null);
  }
}

// Apply in routing module
RouterModule.forRoot(routes, {
  preloadingStrategy: CustomPreloadingStrategy
})

Change Detection Optimization

OnPush Change Detection Strategy

Reduce change detection cycles by using OnPush strategy for components that don't need frequent updates.


@Component({
  selector: 'app-product-card',
  template: `...`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProductCardComponent {
  @Input() product!: Product;
  @Output() addToCart = new EventEmitter<Product>();

  constructor(private cdr: ChangeDetectorRef) {}

  // Manually trigger change detection when needed
  updateData(newData: any) {
    // Update component state
    this.cdr.markForCheck();
  }
}
Detach from Change Detection

For components with complex calculations or frequent updates, consider detaching from automatic change detection.


@Component({
  template: `
    <div>Heavy computation result: {{ result }}</div>
    <button (click)="performCalculation()">Calculate</button>
  `
})
export class HeavyCalculationComponent implements OnInit {
  result = 0;

  constructor(private cdr: ChangeDetectorRef) {}

  ngOnInit() {
    // Detach from automatic change detection
    this.cdr.detach();
  }

  performCalculation() {
    // Perform heavy calculation
    this.result = this.heavyComputation();
    
    // Manually trigger change detection
    this.cdr.detectChanges();
  }
}

Runtime Performance Techniques

1. Virtual Scrolling

Handle large lists efficiently with Angular CDK's virtual scrolling:


// Install Angular CDK
// npm install @angular/cdk

// Component template
<cdk-virtual-scroll-viewport itemSize="50" class="viewport">
  <div *cdkVirtualFor="let item of items; trackBy: trackByFn">
    {{ item.name }}
  </div>
</cdk-virtual-scroll-viewport>

// Component
export class LargeListComponent {
  items = Array.from({length: 100000}, (_, i) => ({
    id: i,
    name: `Item ${i}`
  }));

  trackByFn(index: number, item: any) {
    return item.id; // Use unique identifier
  }
}

2. TrackBy Functions

Optimize *ngFor performance with proper trackBy functions:


// ❌ Bad: Angular recreates DOM elements unnecessarily
<div *ngFor="let user of users">
  {{ user.name }}
</div>

// ✅ Good: Angular reuses existing DOM elements
<div *ngFor="let user of users; trackBy: trackByUserId">
  {{ user.name }}
</div>

// Component
trackByUserId(index: number, user: User): number {
  return user.id;
}

3. Async Pipe and Observables

Use async pipe to automatically handle subscriptions and change detection:


// ❌ Manual subscription management
export class UserComponent implements OnInit, OnDestroy {
  users: User[] = [];
  private subscription = new Subscription();

  ngOnInit() {
    this.subscription.add(
      this.userService.getUsers().subscribe(users => {
        this.users = users;
      })
    );
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

// ✅ Async pipe handles everything
export class UserComponent {
  users$ = this.userService.getUsers();

  constructor(private userService: UserService) {}
}

// Template
<div *ngFor="let user of users$ | async">
  {{ user.name }}
</div>

Image and Asset Optimization

1. Angular Image Directive (v15+)

Use Angular's optimized image directive for better performance:


// Import NgOptimizedImage in your module
import { NgOptimizedImage } from '@angular/common';

@NgModule({
  imports: [NgOptimizedImage],
  // ...
})

// Template with optimized image
<img
  ngSrc="assets/hero-image.jpg"
  width="800"
  height="600"
  priority> <!-- For above-the-fold images -->

<img
  ngSrc="assets/product-{{ product.id }}.jpg"
  width="300"
  height="200"
  loading="lazy"> <!-- For below-the-fold images -->

2. Service Worker and Caching

Implement service worker for offline support and caching:


// Add PWA support
ng add @angular/pwa

// Configure caching in ngsw-config.json
{
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "updateMode": "prefetch",
      "resources": {
        "files": ["/favicon.ico", "/index.html", "/*.css", "/*.js"]
      }
    },
    {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": ["/assets/**"]
      }
    }
  ]
}

Performance Monitoring and Analysis

1. Angular DevTools

Use Angular DevTools browser extension to profile your application:

  • Component tree inspection
  • Change detection profiling
  • Dependency injection analysis
  • Route tree exploration

2. Bundle Analysis

Analyze your bundle size with webpack-bundle-analyzer:


// Install bundle analyzer
npm install --save-dev webpack-bundle-analyzer

// Build with stats
ng build --stats-json

// Analyze bundle
npx webpack-bundle-analyzer dist/your-app/stats.json

// Or use Angular CLI's built-in analyzer
ng build --source-map
npx webpack-bundle-analyzer dist/your-app/*.js

3. Web Performance Metrics

Monitor Core Web Vitals and other performance metrics:


// Performance measurement service
@Injectable({ providedIn: 'root' })
export class PerformanceService {
  measureFCP() {
    return new Promise(resolve => {
      new PerformanceObserver((list) => {
        const entries = list.getEntries();
        const fcp = entries.find(entry => entry.name === 'first-contentful-paint');
        resolve(fcp?.startTime);
      }).observe({ type: 'paint', buffered: true });
    });
  }

  measureLCP() {
    return new Promise(resolve => {
      new PerformanceObserver((list) => {
        const entries = list.getEntries();
        const lcp = entries[entries.length - 1];
        resolve(lcp?.startTime);
      }).observe({ type: 'largest-contentful-paint', buffered: true });
    });
  }
}

Advanced Optimization Strategies

Micro-frontend Architecture

Split large applications into smaller, independently deployable modules.

Server-Side Rendering (SSR)

Implement Angular Universal for better initial load times and SEO.

Prerendering

Generate static HTML at build time for improved performance.


// Add Angular Universal
ng add @nguniversal/express-engine

// Build and serve with SSR
npm run build:ssr
npm run serve:ssr

// Prerendering with Angular CLI
ng run your-app:prerender

Performance Checklist

Before Production Deployment:

  • ✅ Enable production mode and AOT compilation
  • ✅ Implement lazy loading for feature modules
  • ✅ Use OnPush change detection where appropriate
  • ✅ Optimize images and enable lazy loading
  • ✅ Implement service worker for caching
  • ✅ Analyze bundle size and remove unused dependencies
  • ✅ Use trackBy functions for large lists
  • ✅ Profile with Angular DevTools
  • ✅ Test on different devices and network conditions
  • ✅ Monitor Core Web Vitals

Conclusion

Angular performance optimization is an ongoing process that requires careful attention to both initial load times and runtime performance. By implementing the techniques covered in this guide, you can significantly improve your application's user experience and meet modern web performance standards.

Remember that optimization should be based on actual measurements, not assumptions. Use profiling tools to identify bottlenecks, measure the impact of your optimizations, and continuously monitor your application's performance in production.

The Angular ecosystem continues to evolve with new performance features and improvements. Stay updated with the latest Angular releases and best practices to ensure your applications remain fast and efficient as they grow and evolve.