Tips to improve the performance of your angular app.
Don't just build it. Make it fast. Make it feel good.
📚 Table of Contents
- Introduction
- Use Lazy Loading for Feature Modules
- Use OnPush Change Detection
- Avoid Memory Leaks with Unsubscribing
- TrackBy with *ngFor
- Minimize Bundle Size
- Use Pure Pipes
- Avoid Complex Computation in Templates
- Optimize Third-Party Imports
- Leverage Angular CLI Production Flags
- Use Web Workers for Heavy Lifting
- Story Time: The App That Felt Laggy
- Further Reading
🧭 Introduction
Angular is powerful. But that power comes with responsibility.
A fast app isn't just about speed — it's about the feeling of speed. Users bounce when things lag. These tips will help you build apps that fly.
🧱 Use Lazy Loading for Feature Modules
We covered this in our lazy loading tutorial.
But here's the TL;DR:
{
path: 'dashboard',
loadChildren: () =>
import('./dashboard/dashboard.module').then(m => m.DashboardModule)
}
Lazy loading cuts down the initial bundle size.
⚡ Use OnPush Change Detection
By default, Angular checks everything on every change.
That’s wasteful.
Use ChangeDetectionStrategy.OnPush
for components that rely only on input props.
@Component({
selector: 'app-card',
templateUrl: './card.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CardComponent { }
You’ll see a huge boost in large apps.
🧹 Avoid Memory Leaks with Unsubscribing
Subscriptions in Angular don't auto-clean themselves.
Use:
takeUntil()
withSubject
for manual cleanupasync
pipe when you can
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
Memory leaks kill performance over time.
🔁 TrackBy with *ngFor
When looping over large lists, always use trackBy
.
<li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
trackById(index: number, item: any): number {
return item.id;
}
It prevents DOM re-creation and unnecessary re-rendering.
📦 Minimize Bundle Size
Use source-map-explorer:
npm run build -- --stats-json
npx source-map-explorer dist/main.*.js
You’ll see what’s bloating your bundle.
Then:
- Remove unused dependencies
- Switch to lighter alternatives
- Split code with lazy modules
🧼 Use Pure Pipes
A pure pipe recalculates only when inputs change.
Avoid complex logic in templates. Put it in pipes.
@Pipe({
name: 'capitalize',
pure: true
})
export class CapitalizePipe implements PipeTransform {
transform(value: string): string {
return value.charAt(0).toUpperCase() + value.slice(1);
}
}
⛔ Avoid Complex Computation in Templates
This is bad:
{{ calculateExpensiveValue(user) }}
It runs on every change detection cycle.
Move logic to the component or a memoized service.
📚 Optimize Third-Party Imports
Instead of:
import * as _ from 'lodash';
Use:
import debounce from 'lodash/debounce';
Tree shaking fails when you import everything.
🔧 Leverage Angular CLI Production Flags
Always build for production:
ng build --configuration production
It enables:
- AOT compilation
- Minification
- Dead code elimination
You get a much smaller and faster app.
🧠 Use Web Workers for Heavy Lifting
If you're doing big calculations (image processing, sorting 10,000 items), use Web Workers.
ng generate web-worker heavy-task
Offload work from the main thread.
📖 Story Time: The App That Felt Laggy
Back when I was optimizing the first version of CoScrum, I noticed it felt sluggish.
Not broken. Just... heavy.
I opened Chrome DevTools, hit the Performance tab, and recorded a session.
Turns out, every component was using default change detection and recalculating even when nothing changed.
I switched some key components to OnPush
, added trackBy
, and pulled a charting library out of the main bundle.
It felt like I added a turbo engine.
📚 Further Reading
More tutorials to explore:
- Angular Routers and Lazy Loading
- State Management in Angular
- Reactive Forms vs Template-Driven: Which One Is Faster?
You don’t need to do everything above.
But pick 3–4 changes, test your app again. You’ll feel it.
// Until next time...
const performance = optimizeAngularApp();