DEV Community

Cover image for Your Angular App Is Slower Than It Should Be - @defer Fixes That
hassantayyab
hassantayyab

Posted on

Your Angular App Is Slower Than It Should Be - @defer Fixes That

Below is a complete, polished, and deeply detailed article on Angular’s @defer, written as if for a high-quality technical blog.
It covers everything: what @defer is, why it exists, triggers, placeholders, loading/error states, interaction with SSR/hydration, best practices, performance impact, and — importantly — how it differs from using @if.

If you want, I can also format it for Medium/Dev.to/LinkedIn afterwards.


A Complete Guide to Angular’s @defer: The New Era of Template-Level Lazy Loading

Angular’s modern template syntax introduced a powerful new primitive: deferrable views, powered by the @defer block.
This feature represents one of the biggest shifts in Angular’s performance model — giving developers precise control over when parts of the UI load, how they hydrate, and why the user should or shouldn’t wait for a component to be ready.

If you’ve ever struggled with lazy loading at the router level, or tried hiding components behind conditionals to improve performance, @defer is the feature you always wished Angular had.

This article will teach you everything:

  • What @defer is and why it exists
  • How Angular treats deferred blocks
  • All deferral triggers
  • How placeholders, loading, and error templates work
  • How it affects SSR/hydration
  • Best practices and anti-patterns
  • Why @defer IS NOT the same as @if — and why that matters
  • Real-world examples & patterns

Let’s start with the “why”.


Why @defer Exists (and Why @if Isn’t Enough)

Historically, Angular lazy-loaded only at the route level using the router’s loadChildren.

But component-level lazy loading?
You had to use:

  • Dynamic imports inside components
  • ViewContainerRefs
  • NgComponentOutlet
  • Third-party helper libraries
  • Or… hide heavy UI behind an *ngIf

Except hiding components does not reduce bundle size.

<!-- Does NOT lazy load -->
@if (shouldShowChart) {
  <heavy-chart></heavy-chart>
}
Enter fullscreen mode Exit fullscreen mode

Even if that branch never runs:

  • The component code is still downloaded
  • Angular still compiles it
  • JS bundle remains large
  • Initial render slows down

This is where @defer steps in.


What @defer Does

@defer enables declarative lazy loading at the template level.

That means:

  • The component inside the block is excluded from the initial JS bundle
  • It is only downloaded when needed
  • Angular automatically turns the block into a separately-built chunk
  • You gain fine-grained control over when that chunk downloads

This yields real performance wins:

  • Faster initial page load
  • Earlier interactivity
  • Better Core Web Vitals
  • “Progressively-enhanced UI” without extra code

Basic Example

@defer {
  <heavy-chart></heavy-chart>
}
Enter fullscreen mode Exit fullscreen mode

This alone:

  • Splits heavy-chart into its own lazy chunk
  • Loads it as soon as possible after the initial page is interactive

Angular defaults to “idle-time loading” when no trigger is provided.


Triggers — When Should the Deferred Block Load?

@defer supports several “deferrable triggers”.
You add them in parentheses like:

@defer (on viewport) { ... }
Enter fullscreen mode Exit fullscreen mode

Let’s cover them all.


1. on idle (default)

Loads when the browser is idle — great for non-critical UI.

@defer (on idle) {
  <analytics-widget />
}
Enter fullscreen mode Exit fullscreen mode

Used for:

  • Analytics
  • Charts
  • Comment sections
  • Ads

2. on viewport

Loads when the element enters the viewport.

@defer (on viewport) {
  <promo-banner />
}
Enter fullscreen mode Exit fullscreen mode

Used for:

  • Below-the-fold elements
  • Infinite scroll sections
  • Hero animations

Equivalent to “load when the user scrolls here”.


3. on interaction

Loads when the user clicks/touches/keys anywhere inside the block’s placeholder area.

@defer (on interaction) {
  <payment-form />
}
Enter fullscreen mode Exit fullscreen mode

Examples:

  • “Sign in” form
  • Chat widget
  • Expanded accordion content

4. on hover

Loads on hover event.

@defer (on hover) {
  <product-preview />
}
Enter fullscreen mode Exit fullscreen mode

Great for:

  • Tooltips
  • Quick previews

5. on timer(duration)

Delays loading by a fixed time.

@defer (on timer(2000)) {
  <suggestions />
}
Enter fullscreen mode Exit fullscreen mode

Could be used for:

  • Prefetching
  • Non-essential UI

6. when <condition>

Loads when a boolean condition becomes truthy.

@defer (when isReady()) {
  <checkout-flow />
}
Enter fullscreen mode Exit fullscreen mode

This one feels like @if, but behaves differently (we’ll compare later).


Loading States — @placeholder, @loading, @error

Deferrable views support optional companion blocks.


@placeholder (shown before loading begins)

@defer (on viewport) {
  <product-gallery />
} @placeholder {
  <div class="ph">Loading gallery…</div>
}
Enter fullscreen mode Exit fullscreen mode

Displayed immediately until loading is triggered.


@loading (shown while chunk is downloading)

@loading {
  <spinner />
}
Enter fullscreen mode Exit fullscreen mode

This appears after trigger fires, before actual component is ready.


@error (if the lazy chunk fails to load)

@error {
  <p>Failed to load content.</p>
}
Enter fullscreen mode Exit fullscreen mode

Critical for robustness.


Full Example (Most Common Pattern)

@defer (on viewport) {
  <heavy-chart></heavy-chart>
} @placeholder {
  <p>Chart coming up…</p>
} @loading {
  <p>Loading chart…</p>
} @error {
  <p>Could not load chart.</p>
}
Enter fullscreen mode Exit fullscreen mode

This is the production-ready form of @defer.


Why @defer Is NOT the Same as Using @if

This is where developers often get confused.

Let's clarify the difference clearly and absolutely.


@if does not lazy load anything

@if (showChart) {
  <heavy-chart />
}
Enter fullscreen mode Exit fullscreen mode
  • Component is still bundled in main chunk
  • JS still downloaded
  • Compilation still done
  • Memory still allocated

@if only controls visibility, not loading.


@defer actually lazy loads the code

@defer (on viewport) {
  <heavy-chart />
}
Enter fullscreen mode Exit fullscreen mode
  • Component code is excluded from main bundle
  • Chunk is created separately
  • Angular only loads it when triggered
  • Increases page load speed
  • Improves TTI (Time to Interactive)

Defer vs If: Visual Comparison

Feature @if @defer
Lazy loads component? ❌ No ✅ Yes
Reduces JS bundle size? ❌ No ✅ Yes
Useful for performance? ❌ Not really ⭐ Essential
Controls visibility ✅ Yes ⚠️ Kinda (but different)
Controls when code loads ❌ No ✅ Yes
Needs triggers ❌ No ✅ Yes
SSR friendly ✓ Yes ✓ Yes
Hydration aware ⚠️ Yes ⭐ Optimized

@defer is about performance.
@if is about logic.


Where to Use Each

Use @if for:

  • Conditional UI logic
  • Showing/hiding error messages
  • Mode switching
  • Stateful toggles

Use @defer for:

  • Heavy components
  • Large forms
  • Third-party widgets
  • Charts/maps
  • Anything below-the-fold
  • Expensive initialization

SSR + Hydration Behavior

When using Angular Universal:

With SSR:

  • Server renders the placeholder (!)
  • Browser loads actual heavy content only when triggered
  • Prevents delaying First Contentful Paint (FCP)

Hydration becomes smarter:

  • @defer areas hydrate later
  • Event replay still works
  • Large interactive areas do not block main thread early

This drastically improves:

  • LCP
  • FID
  • TTI
  • CLS stability

Best Practices

✔ Prefer @defer (on viewport) for below-fold content

✔ Combine @placeholder + @loading for smooth UX

✔ Don’t over-defer tiny components

✔ Use @defer (when condition) only when necessary

❌ Don’t rely on @if to optimize heavy components

✔ Defer third-party widgets by default

✔ Defer large forms on e-commerce/product pages


Anti-Patterns

🚫 Deferring above-the-fold core UI

Bad for UX; increases CLS.

🚫 Using @defer instead of routes for whole page sections

Use router lazy-loading for big chunks.

🚫 Using both @if and @defer to hide the same component

Pick one:

  • Want lazy load? ⇒ @defer
  • Want visibility control? ⇒ @if

Practical Real-World Patterns

1. Defer Chat Widget

@defer (on interaction) {
  <chat-support />
} @placeholder {
  <button class="chat-btn">Chat with us</button>
}
Enter fullscreen mode Exit fullscreen mode

User clicks → Chat loads instantly.


2. Defer Stripe or PayPal Payment Form

@defer (on interaction) {
  <payment-checkout />
} @placeholder {
  <button class="pay">Pay Now</button>
}
Enter fullscreen mode Exit fullscreen mode

Avoid shipping heavy payment libraries on page load.


3. Defer Maps

@defer (on viewport) {
  <company-map />
} @placeholder {
  <div class="map-ph"></div>
}
Enter fullscreen mode Exit fullscreen mode

Google Maps is huge — defer it always.


Final Summary

@if

  • UI logic
  • Does not improve performance
  • Does not lazy load components
  • Avoid using it for heavy UI parts

@defer

  • Powerful UI performance optimization tool
  • Lazy loads code at the template level
  • Chunk splitting happens automatically
  • Uses triggers: on viewport, on idle, on interaction, etc.
  • Supports placeholders/loading/error views
  • Helps with SSR, hydration, Core Web Vitals, UX

In modern Angular applications, @defer should be your go-to performance lever for delivering a fast, progressive user experience.

For the official Angular documentation on @defer, see:
👉 https://angular.dev/guide/templates/defer

Top comments (0)