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
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
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();
}
}
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
Split large applications into smaller, independently deployable modules.
Implement Angular Universal for better initial load times and SEO.
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.