Deferrable Views (@defer)
Lazy load components using Angular’s built-in @defer block.
Basic @defer
Section titled “Basic @defer”@Component({ template: ` @defer { <app-comments [albumId]="albumId"></app-comments> } @placeholder { <p>Click to load comments</p> } `})export class DetailComponent { albumId = input.required<number>();}@defer Triggers
Section titled “@defer Triggers”on interaction
Section titled “on interaction”Load when user interacts with the trigger element:
@defer (on interaction) { <app-comments [albumId]="albumId"></app-comments>} @placeholder { <a>Load comments</a>}on viewport
Section titled “on viewport”Load when element enters viewport:
@defer (on viewport) { <app-heavy-chart [data]="chartData"></app-heavy-chart>} @placeholder { <div style="height: 400px">Chart will load when visible</div>}on idle
Section titled “on idle”Load when browser is idle:
@defer (on idle) { <app-analytics-widget></app-analytics-widget>} @placeholder { <div>Loading analytics...</div>}on immediate
Section titled “on immediate”Load immediately (useful for testing):
@defer (on immediate) { <app-widget></app-widget>}on timer
Section titled “on timer”Load after specified milliseconds:
@defer (on timer(5000)) { <app-ads></app-ads>} @placeholder { <div>Ad loads in 5 seconds</div>}on hover
Section titled “on hover”Load when user hovers over trigger:
@defer (on hover) { <app-tooltip [content]="tooltipText"></app-tooltip>} @placeholder { <span>Hover for more info</span>}@loading Block
Section titled “@loading Block”Show content while lazy chunk loads:
@defer (on interaction) { <app-comments></app-comments>} @loading { <p>Loading comments...</p>} @placeholder { <a>Load comments</a>}minimum and after
Section titled “minimum and after”Control when loading state shows:
@defer (on interaction) { <app-comments></app-comments>} @loading (after 100ms; minimum 1s) { <p>Loading...</p>} @placeholder { <a>Load comments</a>}after- Delay before showing loading stateminimum- Minimum time to show loading state (prevents flash)
@error Block
Section titled “@error Block”Handle loading failures:
@defer (on viewport) { <app-comments></app-comments>} @error { <p>Failed to load comments. Please try again.</p>} @placeholder { <div>Comments section</div>}@placeholder Block
Section titled “@placeholder Block”Content shown before deferral triggers:
@defer (on viewport) { <app-heavy-component></app-heavy-component>} @placeholder (minimum 500ms) { <div class="skeleton">Loading...</div>}Multiple Triggers
Section titled “Multiple Triggers”Combine multiple trigger conditions:
@defer (on interaction; on timer(5000)) { <app-widget></app-widget>} @placeholder { <div>Widget loads on click or after 5s</div>}Prefetching
Section titled “Prefetching”Prefetch lazy chunk before showing:
@defer (on interaction; prefetch on idle) { <app-comments></app-comments>} @placeholder { <a>Load comments</a>}Named Triggers
Section titled “Named Triggers”Use specific elements as triggers:
<button #loadBtn>Load Comments</button>
@defer (on interaction(loadBtn)) { <app-comments></app-comments>} @placeholder { <p>Click button above to load</p>}Complete Example
Section titled “Complete Example”@Component({ template: ` <div class="album-detail"> <h1>{{ album.name }}</h1> <p>{{ album.description }}</p>
@defer (on interaction; prefetch on idle) { <app-comments [albumId]="album.id"></app-comments> } @loading (after 200ms; minimum 1s) { <div class="loading-spinner"> <p>Loading comments...</p> </div> } @placeholder { <a class="load-link">Load comments</a> } @error { <p class="error">Failed to load comments.</p> } </div> `})export class AlbumDetailComponent { album = input.required<Album>();}Nested @defer
Section titled “Nested @defer”@defer (on viewport) { <app-section> @defer (on interaction) { <app-nested-component></app-nested-component> } @placeholder { <button>Load more</button> } </app-section>} @placeholder { <div>Section placeholder</div>}Testing Deferrable Views
Section titled “Testing Deferrable Views”import { ComponentFixture, TestBed } from '@angular/core/testing';import { Component } from '@angular/core';
describe('DeferrableViews', () => { it('should render placeholder initially', () => { @Component({ template: ` @defer { <app-comments></app-comments> } @placeholder { <p>Placeholder</p> } ` }) class TestComponent {}
const fixture = TestBed.createComponent(TestComponent); fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Placeholder'); });});When to Use @defer
Section titled “When to Use @defer”✅ Good use cases:
- Heavy components (charts, editors)
- Below-the-fold content
- Components with large dependencies
- Modal/dialog content
- Comments sections
- Admin features
❌ Avoid for:
- Critical above-the-fold content
- Small components
- Components needed immediately
- Navigation elements
Performance Benefits
Section titled “Performance Benefits”// ❌ Bad: Eagerly loads heavy component@Component({ template: ` @if (showComments()) { <app-comments></app-comments> } `})
// ✅ Good: Lazy loads on demand@Component({ template: ` @defer (on interaction) { <app-comments></app-comments> } @placeholder { <a>Load comments</a> } `})Best Practices
Section titled “Best Practices”- Use
@loadingwithafterto avoid loading flashes - Use
minimumto prevent loading flash on fast connections - Prefetch on
idlefor better UX - Use
@errorfor robust error handling - Combine triggers for flexibility
- Use
@placeholder (minimum)to prevent layout shifts - Defer heavy third-party components
- Test deferrable views in slow network conditions
Bundle Size Impact
Section titled “Bundle Size Impact”# Before @defermain.js: 500kb (includes CommentsComponent)
# After @defermain.js: 350kblazy-comments.js: 150kb (loaded on demand)
