Day 15 — Angular Performance Optimization: OnPush, @defer & Lazy Loading
📘 Day 15 — Angular Performance Optimization: OnPush, @defer & Lazy Loading
Zero to Hero — Hands-on Angular Tutorial
Today you will learn:
- ✔️ Change Detection Strategies:
Defaultvs.OnPush - ✔️ Deferrable Views (
@defer): Lazy loading parts of a template - ✔️ Image Optimization:
NgOptimizedImage - ✔️ Code Splitting: Keeping bundle sizes small
Performance is not an afterthought. In Angular, a few small tweaks can make your app 10x faster. ⚡
🟦 1. Change Detection: The “OnPush” Upgrade
By default, Angular checks every component whenever anything happens (click, timer, http). This is safe but can be slow in huge apps.
Switch to OnPush:
This tells Angular: “Only check me if my Inputs change OR if I explicitly tell you to.”
import { Component, ChangeDetectionStrategy, input } from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
template: `...`,
// 🚀 PERFORMANCE BOOST ENABLED
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserCardComponent {
user = input.required<User>();
// Angular will ONLY re-render if the parent passes a NEW 'user' object reference.
}
- Default: Checks entire tree on every event.
- OnPush: Checks only when Inputs (
@Input) change reference (old !== new). - Best Practice: Use
OnPushfor almost ALL presentation/dumb components.
🟩 2. Deferrable Views (@defer) — Angular 17+ Feature
Historically, lazy loading was only for Routes. Now, you can lazy load specific chunks of your HTML.
Scenario: You have a heavy Google Map or Chart component. You don’t want to load its code until the user actually sees it.
<!-- Main content loads instantly -->
<h1>Dashboard</h1>
<!-- 💤 This chunk waits! -->
@defer (on viewport) {
<app-heavy-chart></app-heavy-chart>
} @placeholder {
<p>Scroll down to load chart...</p>
} @loading {
<div class="spinner">Loading...</div>
} @error {
<p>Failed to load chart.</p>
}
Triggers:
on viewport: When element enters screen.on interaction: When user clicks/interacts with placeholder.on idle: When browser is effectively doing nothing (background load).on timer(5s): After 5 seconds.
This drastically reduces your “Initial Bundle Size”. 📉
🟧 3. Image Optimization (NgOptimizedImage)
Images are the #1 cause of slow websites (LCP - Largest Contentful Paint). Angular has a built-in directive that auto-optimizes them.
Old HTML:
<img src="large-image.jpg" width="500" height="300">
Angular Way:
- Import
NgOptimizedImage. - Use
ngSrcinstead ofsrc.
import { NgOptimizedImage } from '@angular/common';
@Component({
imports: [NgOptimizedImage],
template: `
<img
ngSrc="large-image.jpg"
width="500"
height="300"
priority <!-- ⭐ Critical for LCP elements (Hero images) -->
/>
`
})
Benefits:
- Enforces Lazy Loading (defaults to lazy).
- Warns if image size is wrong.
- Prevents layout shift (CLS).
- Auto-generates
srcsetif configured with a CDN.
🟥 4. TrackBy in For Loops (Legacy vs Modern)
The Old Problem: when an array changes, Angular re-renders the entire listDOM.
Old Solution (*ngFor with trackBy):
<div *ngFor="let user of users; trackBy: trackById">
{{ user.name }}
</div>
Modern Solution (@for with track):
In Angular 17+, track is mandatory and cleaner.
@for (user of users; track user.id) {
<div class="card">{{ user.name }}</div>
}
Why?
If you modify one user in a list of 1,000, Angular now only updates DOM for that one user, instead of re-rendering 1,000 .card divs. Huge speed difference for sorting/filtering lists.
🟫 5. Build Analyzer (Webpack/Esbuild)
How do you know if your app is fat?
Run this:
ng build --stats-json
Then analyze dist/stats.json using tools like webpack-bundle-analyzer to see exactly which libraries (moment.js? lodash?) are bloating your app.
🎉 End of Day 15 — What You Learned
Today you tackled the “Invisible” features that make apps feel professional:
- ✔️ OnPush: Reducing change detection cycles.
- ✔️ @defer: Granular code splitting for components.
- ✔️ ngSrc: Automated image best practices.
- ✔️ @for track: optimized list rendering.
Your app is now lean, mean, and fast. 🏎️
🧪 Day 15 Challenge
Optimize an existing “Heavy Dashboard” component.
Requirements:
- Create a “Heavy” component (simulate it with a large list or just log “Loading Heavy…”).
- Use
@defer (on interaction)so it only loads when the user clicks a “Load Stats” button. - Implement a list of 1000 items using
@forwithtrack. - Add a large image using
ngSrcwithpriorityset. - Convert the component to
ChangeDetectionStrategy.OnPush.