Introduction: Performance Is a Feature
In a market where 53% of mobile users abandon a page that takes more than 3 seconds to load (source: Google/SOASTA), performance is not an optional optimization—it is a product requirement. For Angular applications, this translates into precise architectural choices, built-in profiling tools, and intelligent rendering strategies.
In this guide, we explore the most effective techniques to optimize an Angular application—from compilation to bundling, from change detection to HTTP caching— with real code examples and reference metrics.
Topics: #Angular #Performance #LazyLoading #ChangeDetection #WebDevelopment
Section 1: AOT Compilation and Optimized Build
Ahead-of-Time (AOT) Compilation
Angular compiles HTML templates into JavaScript during the build phase, not at runtime in the browser. Since Angular 9, AOT compilation is enabled by default in production and provides three measurable benefits:
- Smaller bundle: the Angular compiler (
@angular/compiler) is not included in the final bundle. - Faster rendering: the browser receives precompiled JavaScript, not templates to interpret.
- Early error detection: invalid bindings and type mismatches are caught at build time.
# Production build with AOT (enabled by default)
ng build --configuration production
# Verify AOT is enabled
# angular.json → projects → [name] → architect → build → configurations → production
# "aot": true
Tree Shaking and Bundle Analysis
Tree shaking is the process where the bundler (esbuild in Angular 17+) removes unused exported code. The result is a significantly smaller final bundle.
// ❌ Importing entire module — prevents tree shaking
import * as _ from 'lodash';
const result = _.chunk([1, 2, 3, 4], 2);
// ✅ Specific import — tree-shakeable
import chunk from 'lodash/chunk';
const result = chunk([1, 2, 3, 4], 2);
// ✅ Even better: use native JS
const result = [[1, 2], [3, 4]];
Visual Bundle Analysis
Before optimizing, measure. The webpack-bundle-analyzer plugin generates
an interactive map of your bundle.
npm install --save-dev webpack-bundle-analyzer source-map-explorer
ng build --configuration production --source-map
npx source-map-explorer dist/my-app/browser/*.js
Section 2: Lazy Loading — Load Only What You Need
Route-Level Lazy Loading
Lazy loading splits the application into separate chunks. The browser downloads only the current route’s chunk, reducing Time To Interactive (TTI).
// app.routes.ts
import { Routes } from '@angular/router';
export const APP_ROUTES: Routes = [
{
path: '',
loadComponent: () =>
import('./features/home/home.component').then(c => c.HomeComponent),
},
{
path: 'admin',
loadChildren: () =>
import('./features/admin/admin.routes').then(m => m.ADMIN_ROUTES),
},
{
path: 'blog',
loadChildren: () =>
import('./features/blog/blog.routes').then(m => m.BLOG_ROUTES),
},
];
Preloading Strategy
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withPreloading, PreloadAllModules } from '@angular/router';
import { APP_ROUTES } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(APP_ROUTES, withPreloading(PreloadAllModules)),
],
};
Section 3: Change Detection — Angular’s Performance Nerve Center
OnPush Strategy
// blog-card.component.ts
import {
Component, Input, ChangeDetectionStrategy
} from '@angular/core';
@Component({
selector: 'app-blog-card',
standalone: true,
templateUrl: './blog-card.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BlogCardComponent {
@Input({ required: true }) post!: any;
}
// ❌ Mutation (not detected)
this.post.title = 'New Title';
// ✅ New reference (detected)
this.post = { ...this.post, title: 'New Title' };
Angular Signals
// counter.component.ts
import { Component, signal, computed, effect } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<p>Count: {{ count() }}</p>
<p>Double: {{ doubled() }}</p>
<button (click)="increment()">+1</button>
`,
})
export class CounterComponent {
readonly count = signal(0);
readonly doubled = computed(() => this.count() * 2);
constructor() {
effect(() => console.log('Updated:', this.count()));
}
increment() {
this.count.update(n => n + 1);
}
}
Section 4: HTTP Optimization
HTTP Cache with Interceptor
// cache.interceptor.ts
import { HttpInterceptorFn, HttpResponse } from '@angular/common/http';
import { of } from 'rxjs';
import { tap } from 'rxjs/operators';
const cache = new Map<string, any>();
const TTL = 5 * 60 * 1000;
export const cacheInterceptor: HttpInterceptorFn = (req, next) => {
if (req.method !== 'GET') return next(req);
const cached = cache.get(req.url);
if (cached && Date.now() - cached.time < TTL) {
return of(cached.response);
}
return next(req).pipe(
tap(event => {
if (event instanceof HttpResponse) {
cache.set(req.url, { response: event, time: Date.now() });
}
})
);
};
Section 5: Image Optimization
NgOptimizedImage
// component.ts
import { NgOptimizedImage } from '@angular/common';
@Component({
standalone: true,
imports: [NgOptimizedImage],
template: `
<img
ngSrc="/assets/hero.webp"
width="1200"
height="600"
priority
alt="Hero"
/>
`,
})
Image Compression
cwebp -q 80 input.jpg -o output.webp
avifenc --quality 60 input.jpg output.avif
npx svgo --input assets/icons --recursive
Section 6: Third-Party Dependencies
// ❌ Heavy dependency
import moment from 'moment';
// ✅ Native alternative
const formatted = new Intl.DateTimeFormat('en-US').format(new Date());
// ✅ Lightweight alternative
import { format } from 'date-fns';
const formatted = format(new Date(), 'dd/MM/yyyy');
Section 7: Profiling Tools
Lighthouse CLI
npm install -g lighthouse
lighthouse https://your-site.com \
--output html \
--output-path ./report.html
Summary: Angular Optimization Checklist
- ✅ Production build with AOT
- ✅ Lazy loading enabled
- ✅ OnPush strategy used
- ✅ trackBy in lists
- ✅ HTTP caching
- ✅ Optimized images
- ✅ Minimal dependencies
Conclusion
Optimizing an Angular application is not a one-time tweak but an ongoing discipline. It blends architecture, coding practices, and continuous measurement.
Always start by measuring: Lighthouse and profiling tools will reveal the real bottlenecks, helping you avoid premature optimizations.
#Angular #Performance #FrontendDevelopment