Skip to main content

Enhancing Angular Performance With Deferrable Views

· 4 min read
Sivabharathy

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.