#angular #performance #optimization #lazy-loading

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: Default vs. 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 OnPush for 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:

  1. Import NgOptimizedImage.
  2. Use ngSrc instead of src.
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 srcset if 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:

  1. Create a “Heavy” component (simulate it with a large list or just log “Loading Heavy…”).
  2. Use @defer (on interaction) so it only loads when the user clicks a “Load Stats” button.
  3. Implement a list of 1000 items using @for with track.
  4. Add a large image using ngSrc with priority set.
  5. Convert the component to ChangeDetectionStrategy.OnPush.