Deferrable Views (@defer)
Lazy load components using Angular's built-in @defer block.
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
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
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
Load when browser is idle:
@defer (on idle) {
<app-analytics-widget></app-analytics-widget>
} @placeholder {
<div>Loading analytics...</div>
}
on immediate
Load immediately (useful for testing):
@defer (on immediate) {
<app-widget></app-widget>
}
on timer
Load after specified milliseconds:
@defer (on timer(5000)) {
<app-ads></app-ads>
} @placeholder {
<div>Ad loads in 5 seconds</div>
}
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
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
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
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
Content shown before deferral triggers:
@defer (on viewport) {
<app-heavy-component></app-heavy-component>
} @placeholder (minimum 500ms) {
<div class="skeleton">Loading...</div>
}
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
Prefetch lazy chunk before showing:
@defer (on interaction; prefetch on idle) {
<app-comments></app-comments>
} @placeholder {
<a>Load comments</a>
}
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
@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
@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
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
✅ 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
// ❌ 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
- 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
# Before @defer
main.js: 500kb (includes CommentsComponent)
# After @defer
main.js: 350kb
lazy-comments.js: 150kb (loaded on demand)