Ir al contenido

Deferred Loading

Esta página aún no está disponible en tu idioma.

Deferred loading in Angular allows you to defer the rendering and initialization of components and content until they’re actually needed. Rather than rendering everything when a page loads, Angular can render high-priority content first and load lower-priority content progressively.

The @defer block (introduced in Angular v17) is a built-in control flow feature that makes deferred loading easier and more powerful than ever before.


Only render what users see immediately. Defer everything below the fold, reducing initial rendering time and bundle size.

Deferred loading improves:

  • Largest Contentful Paint (LCP) — Less content to render initially
  • Cumulative Layout Shift (CLS) — Content loads progressively
  • Total Blocking Time (TBT) — Less JavaScript to parse and execute

Users see content faster. Pages feel snappier because critical content renders first.

Content loads as users scroll or interact, creating a seamless experience.


The @defer block is Angular’s built-in way to defer rendering of a template section. It’s part of the new control flow syntax (alongside @if, @for, @switch).

@defer {
<app-heavy-component></app-heavy-component>
} @placeholder {
<p>Loading...</p>
}
  1. Component loads without the deferred content
  2. When trigger condition is met (e.g., user scrolls to it), Angular starts loading
  3. While loading, Angular renders the placeholder
  4. Once loaded, the actual component replaces the placeholder

Defer Below the Fold:

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-product-page',
standalone: true,
imports: [CommonModule],
template: `
<div class="product-hero">
<img [src]="product.image" alt="Product">
<h1>{{ product.name }}</h1>
<button (click)="addToCart()">Add to Cart</button>
</div>
<!-- Defer reviews section below the fold -->
@defer {
<app-reviews [productId]="product.id"></app-reviews>
} @placeholder {
<div class="reviews-skeleton">
<p>Loading reviews...</p>
</div>
}
`
})
export class ProductPageComponent {
product = {
id: 1,
name: 'Product Name',
image: '/image.jpg'
};
addToCart(): void {
}
}

Trigger on Interaction:

@defer (on interaction) {
<app-chat-widget></app-chat-widget>
} @placeholder {
<button class="chat-button">Open Chat</button>
}

Trigger on Viewport (when scrolled into view):

@defer (on viewport) {
<app-recommendations></app-recommendations>
} @placeholder {
<div class="recommendations-skeleton">
<p>Loading recommendations...</p>
</div>
}

Trigger Immediately (on timer):

@defer (on timer(2000)) {
<app-analytics-dashboard></app-analytics-dashboard>
} @placeholder {
<p>Loading dashboard...</p>
}
@defer (on viewport; on interaction; on timer(5000)) {
<app-premium-content></app-premium-content>
} @placeholder {
<div class="premium-skeleton">Premium content loading...</div>
}
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { trigger, transition, style, animate } from '@angular/animations';
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [CommonModule],
animations: [
trigger('fadeIn', [
transition(':enter', [
style({ opacity: 0 }),
animate('300ms ease-in', style({ opacity: 1 }))
])
])
],
template: `
<div class="dashboard">
<!-- Critical content - render immediately -->
<div class="dashboard-header">
<h1>Dashboard</h1>
<p>Welcome, {{ userName }}</p>
</div>
<!-- Secondary content - defer on viewport -->
@defer (on viewport) {
<div class="analytics-section" [@fadeIn]>
<app-analytics></app-analytics>
</div>
} @placeholder {
<div class="skeleton">
<div class="skeleton-item"></div>
<div class="skeleton-item"></div>
</div>
} @error {
<div class="error-message">
<p>Failed to load analytics. Please try again.</p>
<button (click)="retryAnalytics()">Retry</button>
</div>
}
<!-- Heavy components - defer on interaction -->
@defer (on interaction) {
<div [@fadeIn]>
<app-advanced-filters></app-advanced-filters>
</div>
} @placeholder {
<button class="filters-button">Show Advanced Filters</button>
} @error {
<p>Could not load filters</p>
}
<!-- Minimal priority content - defer on timer -->
@defer (on timer(3000)) {
<div [@fadeIn]>
<app-promotions></app-promotions>
</div>
} @placeholder {
<div class="skeleton"></div>
}
</div>
`,
styles: [`
.skeleton {
height: 200px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
`]
})
export class DashboardComponent {
userName = 'John Doe';
retryAnalytics(): void {
}
}

@defer (on interaction) {
<app-modal></app-modal>
} @placeholder {
<button>Open Modal</button>
}

Waits for user interaction (click, focus, keyboard) on the placeholder.

@defer (on viewport) {
<app-heavy-list></app-heavy-list>
} @placeholder {
<p>Loading list...</p>
}

Loads when the placeholder enters the viewport.

With Margin (preload before it’s visible):

@defer (on viewport(200px)) {
<app-images></app-images>
} @placeholder {
<p>Loading images...</p>
}
@defer (on timer(2500)) {
<app-background-task></app-background-task>
} @placeholder {
<p>Processing in background...</p>
}

Loads after specified milliseconds.

@defer (on hover) {
<app-preview></app-preview>
} @placeholder {
<button>Hover to Preview</button>
}

Loads when user hovers over the placeholder.

Load on ANY condition:

@defer (on viewport; on interaction; on timer(5000)) {
<app-card></app-card>
} @placeholder {
<div class="loading">Loading...</div>
}

Load on ALL conditions (use comma):

@defer (on viewport, on idle) {
<app-expensive-content></app-expensive-content>
} @placeholder {
<p>Waiting to load...</p>
}

Shows while content is loading:

@defer (on viewport) {
<app-product-reviews></app-product-reviews>
} @placeholder {
<div class="skeleton">
<div class="skeleton-line"></div>
<div class="skeleton-line"></div>
<div class="skeleton-line"></div>
</div>
}

Explicit loading state (if needed):

@defer (on viewport) {
<app-comments></app-comments>
} @loading (after 200ms; minimum 1s) {
<p>Loading comments...</p>
} @placeholder {
<!-- Initial skeleton -->
<div class="comment-skeleton"></div>
}

Handle loading failures:

@defer (on viewport) {
<app-recommendations></app-recommendations>
} @error {
<div class="error">
<p>Could not load recommendations</p>
<button (click)="retry()">Retry</button>
</div>
} @placeholder {
<p>Loading...</p>
}

Prefetch content before user interactions:

@defer (on interaction; on timer(5000)) {
<app-checkout></app-checkout>
} @placeholder {
<button>Proceed to Checkout</button>
}

This loads on either interaction OR after 5 seconds.

@if (showPremium) {
@defer (on viewport) {
<app-premium-features></app-premium-features>
} @placeholder {
<p>Loading premium...</p>
}
}
import { Component } from '@angular/core';
@Component({
selector: 'app-section',
standalone: true,
template: `
<h2>Section Title</h2>
@defer (on viewport) {
<app-heavy-component></app-heavy-component>
} @placeholder {
<p>Loading...</p>
}
`
})
export class SectionComponent {}
@defer (on viewport; minimum 500ms) {
<app-data></app-data>
} @placeholder {
<div class="loader">Loading...</div>
}

Do defer components below the fold:

<app-hero></app-hero>
@defer (on viewport) {
<app-features></app-features>
}

Do use meaningful placeholders:

@defer (on viewport) {
<app-user-card></app-user-card>
} @placeholder {
<div class="user-card-skeleton">
<div class="avatar-skeleton"></div>
<div class="name-skeleton"></div>
</div>
}

Do combine triggers strategically:

@defer (on viewport; on timer(3000)) {
<app-testimonials></app-testimonials>
}

Don’t defer critical content:

@defer (on viewport) {
<app-main-content></app-main-content>
}
<app-main-content></app-main-content>

Don’t create janky placeholders:

@defer (on viewport) {
<div class="card" style="height: 400px">...</div>
} @placeholder {
<p>Loading...</p>
}
@defer (on viewport) {
<div class="card" style="height: 400px">...</div>
} @placeholder {
<div class="card-skeleton" style="height: 400px"></div>
}

Don’t overuse deferred loading:

@defer (on viewport) { <app-nav></app-nav> }
@defer (on viewport) { <app-header></app-header> }
@defer (on viewport) { <app-main></app-main> }
<app-nav></app-nav>
<app-header></app-header>
<app-main></app-main>
@defer (on viewport) { <app-sidebar></app-sidebar> }

@Component({
template: `
<!-- Immediate: Hero and CTA -->
<app-product-hero [product]="product"></app-product-hero>
<button>Buy Now</button>
<!-- Deferred: Secondary info -->
@defer (on viewport) {
<app-product-reviews></app-product-reviews>
} @placeholder {
<div class="skeleton"></div>
}
@defer (on viewport) {
<app-related-products></app-related-products>
} @placeholder {
<div class="skeleton"></div>
}
`
})
export class ProductComponent {}
@Component({
template: `
<!-- Critical metrics -->
<app-key-metrics></app-key-metrics>
<!-- Secondary charts -->
@defer (on viewport) {
<app-sales-chart></app-sales-chart>
} @placeholder {
<div class="chart-skeleton"></div>
}
<!-- Optional content -->
@defer (on interaction) {
<app-advanced-analytics></app-advanced-analytics>
} @placeholder {
<button>View Analytics</button>
}
`
})
export class DashboardComponent {}
@Component({
template: `
<!-- Post header and intro -->
<app-post-header></app-post-header>
<!-- Main content -->
<app-post-content></app-post-content>
<!-- Below fold content -->
@defer (on viewport) {
<app-comments></app-comments>
}
@defer (on viewport) {
<app-related-posts></app-related-posts>
}
`
})
export class PostComponent {}

Aspect@deferLazy Routes
ScopeComponent contentEntire route
TriggerScroll, interaction, timerRoute navigation
Best ForIn-page contentFeature modules
PerformanceFaster LCPIndependent chunks
@defer (on viewport) {
<app-heavy></app-heavy>
} @placeholder {
<p>Loading...</p>
}
@if (data$ | async as data) {
<app-component [data]="data"></app-component>
}

When to use each:

  • @defer: Defer rendering based on viewport/interaction
  • async pipe: Unwrap observable values in template

Deferred loading with @defer blocks is a powerful way to optimize Angular applications. By loading content progressively, you can:

✅ Improve Core Web Vitals and search ranking
✅ Reduce initial load time
✅ Create snappier user experiences
✅ Reduce initial bundle size

Key Takeaways:

  • Load critical content immediately
  • Defer secondary and below-fold content
  • Use appropriate triggers for your use case
  • Create meaningful placeholders
  • Test Core Web Vitals improvements

The @defer block makes progressive loading easier than ever in Angular.


This guide is based on and inspired by: