Overview
Angular applications often require the efficient management of component loading to enhance performance and user experience. One effective technique for achieving this is by using deferrable views, also known as @defer
blocks. Deferrable views allow developers to defer the loading of select dependencies within a component template, such as components, directives, pipes, and associated CSS. By wrapping a section of your template in a @defer
block, you can specify the conditions under which these dependencies should be loaded, significantly improving the initial load time and overall performance of your application.
What are Deferrable Views?
Deferrable views are a powerful feature in Angular that enable developers to defer the loading of certain parts of the application until they are actually needed. This can help reduce the initial bundle size, improve load times, and enhance Core Web Vitals (CWV) metrics such as Largest Contentful Paint (LCP) and Time to First Byte (TTFB).
Benefits of Deferrable Views
- Reduced Initial Load Time: By deferring the loading of non-essential components, the initial bundle size is reduced, leading to faster initial load times.
- Improved Core Web Vitals: Deferring components can improve metrics like LCP and TTFB, which are critical for user experience and SEO.
- Optimized Resource Utilization: Resources such as memory and CPU are better utilized as only necessary components are loaded initially.
Implementation of Deferrable Views
Deferrable views in Angular are implemented using @defer
blocks, which allow you to specify the loading conditions for the deferred content. These blocks can also include sub-blocks for handling different stages of the loading process, such as placeholders, loading states, and error states.
Example Usage
@defer {
<large-component />
} @placeholder (minimum 500ms) {
<p>Loading...</p>
} @loading (after 100ms; minimum 1s) {
<p>Still loading, please wait...</p>
} @error {
<p>Failed to load component</p>
}
In this example:
- The
@defer
block specifies the main content to be deferred. - The
@placeholder
block provides content to be displayed initially until the main content is loaded. - The
@loading
block displays a loading message if the content takes longer to load. - The
@error
block shows an error message if the loading fails.
Triggers
Deferrable views support various triggers to determine when the deferred content should be loaded:
- on idle: Loads content when the browser is idle.
- on viewport: Loads content when it enters the viewport.
- on interaction: Loads content upon user interaction (click or keydown).
- on hover: Loads content when the user hovers over the specified element.
- on immediate: Loads content immediately after the initial render.
- on timer: Loads content after a specified duration.
Prefetching
Prefetching allows you to specify conditions for preloading dependencies before they are actually needed. This can help reduce the time it takes to display deferred content when it is triggered.
@defer (on interaction; prefetch on idle) {
<calendar-cmp />
} @placeholder {
<img src="placeholder.png" />
}
In this example, the prefetching starts when the browser becomes idle, and the content is rendered upon user interaction.
Testing Deferrable Views
Angular provides tools for testing @defer
blocks. You can manually control the loading states during tests to ensure that the deferred content behaves as expected.
it('should render a defer block in different states', async () => {
TestBed.configureTestingModule({deferBlockBehavior: DeferBlockBehavior.Manual});
@Component({
template: `
@defer {
<large-component />
} @placeholder {
Placeholder
} @loading {
Loading...
}
`
})
class ComponentA {}
const componentFixture = TestBed.createComponent(ComponentA);
const deferBlockFixture = (await componentFixture.getDeferBlocks())[0];
expect(componentFixture.nativeElement.innerHTML).toContain('Placeholder');
await deferBlockFixture.render(DeferBlockState.Loading);
expect(componentFixture.nativeElement.innerHTML).toContain('Loading');
await deferBlockFixture.render(DeferBlockState.Complete);
expect(componentFixture.nativeElement.innerHTML).toContain('large works!');
});
Server-Side Rendering (SSR) and Static Site Generation (SSG)
When using SSR or SSG, @defer
blocks always render their placeholders on the server, and triggers are ignored until the client side takes over.
Best Practices
- Avoid Layout Shifts: Do not defer components visible in the user's viewport on initial load to prevent layout shifts and negative impact on Core Web Vitals.
- Use Standalone Components: Only standalone components, directives, and pipes can be deferred.
- Handle Nested
@defer
Blocks Carefully: Ensure that nested deferrable blocks do not trigger simultaneously to avoid cascading loads.
Conclusion
Deferrable views in Angular are a powerful tool for optimizing application performance by deferring the loading of non-essential components. By using @defer
blocks and carefully managing the loading conditions, you can significantly improve the initial load time, enhance user experience, and achieve better performance metrics. Implementing these practices will help create faster, more efficient Angular applications that delight users and perform well under varying conditions.