DEV Community

Giorgio Galassi
Giorgio Galassi

Posted on

Angular v18+ — Understanding @defer: Blocks, Triggers, and Deferrable Views (Part 1) 🔥🚀

Angular v18 continues to introduce powerful new features, some aimed at simplifying development workflows, such as the @let syntax, redirectTo as a function, and ng-content fallback content. Other functionalities, however, are designed to enhance application performance.

What is Angular’s @defer Block?

The @defer block is part of Angular's newly introduced control-flow syntax family (@if, @for, @switch, and more). This feature allows developers to delay the loading of specific elements until certain pre-determined triggers are met. These triggers can be selected from a predefined list or customized. You can also create a sequence of triggers to control exactly when the component is loaded.

@defer {
  <my-very-heavy-component />
}
Enter fullscreen mode Exit fullscreen mode

Why use Angular’s deferrable views for optimized performance?

Deferrable views offer the advantage of significantly reducing the initial bundle size. By deferring a view, you can avoid loading a heavy component until it’s needed, improving both performance and user experience. For example, you might delay loading a component that becomes relevant later in the application’s flow.

Note: Not all components are deferrable. To use @defer, a component must be standalone, and it's essential to avoid referencing it outside the @defer block (e.g., using @ViewChild).*

Starting with Angular v19, newly created components will be standalone by default, meaning they will also be deferrable by default.

Exploring @defer Blocks: Syntax and Usage

This section breaks down the different @defer sub-blocks:

  • @placehodelr\: Displays default content while the deferred component is loading.
  • @loading\: Shows a loading indicator while the deferred content is being fetched.
  • @error\: Displays a fallback message if the deferred content fails to load.

Note: All components, directives, or pipes used within any of these sub-blocks are eagerly loaded.*

How to Combine Multiple Triggers with @defer in Angular v18

Angular offers various triggers for controlling when deferred content is loaded:

  • on idle: Loads when the browser is idle; it’s the default trigger for the @defer block.
  • on viewport: Loads when the content enters the viewport.
  • on interaction: Loads based on specific user interactions, such as clicks or form submissions.
  • on hover: Triggers when the user hovers over the element.
  • on immediate: Loads content immediately under specific conditions.
  • on timer: Loads after a specified time delay.
  • when: Specifies a condition as an expression that returns a boolean.

You can also combine triggers to create complex loading scenarios, such as loading content when it enters the viewport or when the user hovers over it.

<!-- More than one `on` example -->
<!-- Multiple on triggers are always `OR` conditions -->
@defer (on viewport; on timer(5s)) {
  <my-very-heavy-component />
} @placeholder {
  <img src="placeholder.png" />
}

<!-- Defer `when` example -->
@defer (when cond) {
  <my-very-heavy-component />
}

<!-- Defer `on` and `when` example -->
<!-- `on` and `where` conditions are always `OR` conditions -->
@defer (on viewport; when cond) {
  <calendar-cmp />
} @placeholder {
  <img src="placeholder.png" />
}
Enter fullscreen mode Exit fullscreen mode

Best Practices for Using @defer in Angular v18: Tips and Considerations

To use @defer effectively, it’s essential to understand potential performance implications and optimize its usage:

  • Avoiding Layout Shifts: Ensure that deferred components do not cause layout shifts that could impact the user experience.
  • Standalone Components Only: Make sure components used with @defer are standalone to avoid dependency conflicts.
  • Combining Triggers Wisely: Use combinations of triggers strategically to maximize performance and user engagement.

Nested @defer Blocks

It is possible to have nested @defer blocks, with each block respecting and waiting for its own trigger. To prevent unexpected behavior, it is recommended to assign different triggers to each block, unless you intentionally want multiple blocks to respond to the same triggers.

Below, you can find a StackBlitz project where you can explore nested @defer blocks:

At the time of this article’s publication, the StackBlitz contains examples of:

  • on interaction
  • on viewport
  • Nested @defer blocks

To observe the deferred loading in action, open the network tab in your browser’s developer tools and filter by JavaScript (JS) to see only the downloaded files. Experiment with the application, and you will notice different chunks of components being loaded in a deferred manner.

Conclusion

Starting with Angular v18, the @defer feature provides a powerful way to optimize your application’s loading strategy. By leveraging triggers and fallback blocks (@placeholder, @loading, @error), you can create efficient, user-friendly experiences.

Adopting a “deferrable-first” mindset is both useful and important, as it allows us to deliver smoother user experiences, thanks in part to a lighter bundle.

Future articles will dive deeper into advanced @defer usage and its integration with other Angular features, so stay tuned!

Next steps

Prefetching

The @defer block also supports configuration for pre-fetching components. Prefetching is a process where we anticipate where the user might navigate after a specific action and begin loading resources that could be needed. In Angular, this can be achieved using the @defer block. The prefetch syntax works similarly to main @defer conditions; when and/or on can be used to declare different triggers.

@defer (on interaction; prefetch on idle) {
  <calendar-cmp />
} @placeholder {
  <img src="placeholder.png" />
}
Enter fullscreen mode Exit fullscreen mode

Incremental Hydration

Hydration transforms a pre-rendered HTML page (produced by server-side rendering) into a fully interactive Angular application on the client side. With incremental hydration (here the complete RFC), you can determine when specific parts of your application need to become interactive.

This helps improve Core Web Vitals and provides a faster initial paint with better performance for the end-user.

<!-- Hydrate `when` example -->
@defer (hydrate when user() !== null){
  <some-cmp />
}

<!-- Hydrate `on` example -->
@defer (hydrate on interaction) {
  <some-cmp />
}

<!-- Defer plus Hydrate `on` example -->
@defer (on viewport; hydrate on interaction) {
  <some-cmp />
}
Enter fullscreen mode Exit fullscreen mode

If you found this helpful, follow me here and on LinkedIn for more deep dives into Angular, web performance, and modern frontend development.

See you in the next one! 🤙🏻
— G.

Top comments (0)