White Letter A with Orange edges

How to Implement Deferrable Views in Angular 17 for Faster Load Times

bytebantz
JavaScript in Plain English
7 min readApr 14, 2024

--

Among the array of optimization strategies, deferrable views, also known as @defer blocks, emerge as a powerful tool to streamline initial load times and enhance user experience. Deferrable views offer developers the ability to defer the rendering of certain components until they are needed. By utilizing @defer blocks strategically, developers can delay the loading of non-critical components, reducing the initial bundle size and enhancing the perceived speed of the application. In this article, we delve into the benefits, syntax, and implementation of deferrable views in Angular applications.

Why use Deferrable Views?

Deferrable views, also referred to as @defer blocks, offer several compelling benefits:

  1. Reduced Initial Bundle Size: By deferring the loading of certain dependencies, developers can minimize the initial bundle size of their Angular applications. This reduction in initial payload size contributes to faster load times and improved performance.
  2. Deferring heavy components until later can significantly improve metrics such as Largest Contentful Paint (LCP measures the time it takes for the largest content element within the viewport to become visible to the user) and Time to First Byte (TTFB measures the time taken for the browser to receive the first byte of data from the server after initiating a request).
  3. Improved User Experience: Deferrable views contribute to a smoother user experience by prioritizing the rendering of critical content while non-essential components load in the background.

Blocks in Deferrable Views

@defer

The primary @defer block encapsulates the content that is lazily loaded.

This content remains unrendered initially and becomes visible once specified triggers or conditions are met, and dependencies are fetched.

By default, a @defer block is triggered when the browser state becomes idle.

@placeholder

The optional @placeholder block allows developers to specify content to display before the @defer block is triggered.

This placeholder content serves as a temporary visual indicator and is replaced with the main content upon completion of loading.

The @placeholder block accepts an optional parameter (minimum) to specify the minimum amount of time that this placeholder should be shown.

This parameter (minimum) exists to prevent fast flickering of placeholder content in the case that the deferred dependencies are fetched quickly.

@defer {
<large-component />
} @placeholder (minimum 500ms) {
<p>Placeholder content</p>
}

@loading

The @loading block allows you to declare content that will be shown during the loading of any deferred dependencies.

This block is optional and commonly used to show loading indicators such as spinners.

The @loading block accepts two optional parameters (minimum & after) to specify the minimum amount of time that this placeholder should be shown and amount of time to wait after loading begins before showing the loading template.

The @loading state is bypassed when resources have been prefetched.

@defer {
<large-component />
} @loading (after 100ms; minimum 1s) {
<img alt="loading..." src="loading.gif" />
}

@error

In case of deferred loading failure, the @error block allows developers to declare content to be shown. This block, like @placeholder and @loading, is optional.

@defer {
<calendar-cmp />
} @error {
<p>Failed to load the calendar</p>
}

Triggers

Triggers determine when a @defer block is activated, replacing placeholder content with lazily loaded content.

NOTE:

· Nested @defer blocks should have different conditions to prevent cascading requests.

· During server-side rendering (SSR) or static site generation (SSG), @defer blocks render their placeholders by default, disregarding triggers

Below are various triggers that cater to different user interactions and states.

  • on: Specifies trigger conditions using predefined triggers such as idle, viewport, interaction, hover, immediate, and timer.
  • when: Specifies conditions using boolean expressions.

on idle

The idle trigger initiates deferred loading when the browser reaches an idle state. This is the default behavior for a @defer block.

on viewport

The viewport trigger activates deferred loading when the specified content enters the viewport. By default, the placeholder acts as the element watched for entering the viewport.

Alternatively, developers can designate a template reference variable as the element to watch for viewport entry.

<div #notification>Message body..</div>
@defer (on viewport(notification)) {
<notification-popup />
}

on interaction

The interaction trigger initiates deferred loading when the user interacts with the specified element through click or keydown events. By default, the placeholder serves as the interaction element.

Alternatively, developers can specify a template reference variable as the interaction trigger.

<button type="button" #greeting>Hello!</button>
@defer (on interaction(greeting)) {
<greetings-cmp />
}

on hover

The hover trigger triggers deferred loading when the mouse hovers over the specified element.

Alternatively, developers can designate a template reference variable as the hover trigger.

on immediate

The immediate trigger initiates deferred loading immediately after client rendering completes, bypassing any further delays

on timer

The timer trigger activates deferred loading after a specified duration, specified in milliseconds or seconds.

@defer (on timer(500ms)) {
<greetings-cmp />
}

Prefetching

Prefetching enables the proactive fetching of dependencies based on specified conditions, enhancing resource availability before actual usage

The syntax for specifying prefetch conditions is similar to that of the main @defer conditions. This includes the use of when and on to declare the triggers for prefetching

@defer (on interaction; prefetch on idle) {
<product-details-cmp />
}

Example

Now let’s illustrate how to use deferrable views by creating a simple News App to explore how by deferring the rendering of certain components, we can enhance performance, streamline initial load times, and create a better user experience

Run the following command to generate a new project:

ng new news-app

Run the following command to generate a new interface:

ng generate interface article

Now, let’s modify the article.ts file in the src/app directory to implement the Article interface:

export interface Article {
title: string;
imageUrl: string;
comments: string[];
}

Run the following command to generate a new service:

ng generate service news

Now, let’s modify the news.service.ts file in the src/app directory to implement the NewsService:

import { Injectable } from '@angular/core';
import { Article } from './article';

@Injectable({
providedIn: 'root'
})
export class NewsService {
private articles: Article[] = [
{
title: 'Article 1',
imageUrl: 'https://source.unsplash.com/300x300',
comments: ['Article 1 Comment 1', 'Article 1 Comment 2', 'Article 1 Comment 3']
},
{
title: 'Article 2',
imageUrl: 'https://source.unsplash.com/300x300',
comments: ['Article 2 Comment 1', 'Article 2 Comment 2', 'Article 2 Comment 3']
},
{
title: 'Article 3',
imageUrl: 'https://source.unsplash.com/300x300',
comments: ['Article 3 Comment 1', 'Article 3 Comment 2', 'Article 3 Comment 3']
},
// Add more articles as needed
];

constructor() {}

getArticles(): Article[] {
// Simulate fetching articles from an API
return this.articles;
}
}

Run the following command to generate a new component:

ng generate component article

Now, let’s modify the article.component.ts:

import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Article } from '../article';

@Component({
selector: 'app-article',
standalone: true,
imports: [CommonModule],
templateUrl: './article.component.html',
styleUrl: './article.component.css'
})
export class ArticleComponent {
@Input()
article!: Article;

constructor() {}
}

Now, let’s modify the article.component.html to implement the deferrable views:

<div class="article">
<h3>{{ article.title }}</h3>
<!-- Image wrapped in a @defer block -->
@defer (on viewport) {
<img [src]="article.imageUrl" alt="Article Image">
} @placeholder(minimum 500ms) {
<!-- Placeholder content while loading -->
<div class="placeholder">
<p>Image placeholder...</p>
</div>
} @loading {
<!-- Loading indicator -->
<div class="loading">
<img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZTJpbzV1eWpvOHdpZTRjMXF4NGhqMzlleWM1Y2Zwc2U1a292dm9jbSZlcD12MV9naWZzX3NlYXJjaCZjdD1n/uIJBFZoOaifHf52MER/giphy.gif" alt="Loading...">
</div>
} @error {
<!-- Error message -->
<div class="error">
<p>Failed to load image. Please try again later.</p>
</div>
}

<!-- Comments section wrapped in a @defer block -->
@defer (on interaction) {
<div class="comments">
<h4>Comments</h4>
<ul>
<li *ngFor="let comment of article.comments">{{ comment }}</li>
</ul>
</div>
} @placeholder {
<!-- Placeholder content while loading -->
<div class="placeholder">
<p>Comments placeholder...</p>
</div>
} @loading {
<!-- Loading indicator -->
<div class="loading">
<img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZTJpbzV1eWpvOHdpZTRjMXF4NGhqMzlleWM1Y2Zwc2U1a292dm9jbSZlcD12MV9naWZzX3NlYXJjaCZjdD1n/uIJBFZoOaifHf52MER/giphy.gif" alt="Loading...">
</div>
} @error {
<!-- Error message -->
<div class="error">
<p>Failed to load comments. Please try again later.</p>
</div>
}
</div>

Run the following command to generate a new component:

ng generate component news

Now, let’s modify the news.component.ts file to use the NewsService:

import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NewsService } from '../news.service';
import { ArticleComponent } from '../article/article.component';
import { Article } from '../article';

@Component({
selector: 'app-news',
standalone: true,
imports: [CommonModule, ArticleComponent],
templateUrl: './news.component.html',
styleUrl: './news.component.css'
})
export class NewsComponent implements OnInit {
articles: Article[] = [];

constructor(private newsService: NewsService) {}

ngOnInit(): void {
this.articles = this.newsService.getArticles();
}
}

Now, let’s modify the news.component.html to display the news articles:

<div *ngFor="let article of articles">
<app-article [article]="article"></app-article>
</div>

Now, let’s modify the app.component.ts file to use the NewsComponent:

import { Component } from '@angular/core';
import { NewsComponent } from './news/news.component';

@Component({
selector: 'app-root',
standalone: true,
imports: [NewsComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent {
title = 'news-app';
}

Finally, let’s update the app.component.html file to remove the default content and render the news component:

<app-news></app-news>

Now, you can run the application using the following command:

ng serve

Conclusion

In conclusion, Deferrable views in Angular applications offer developers a powerful mechanism to optimize performance and improve user experience. By strategically deferring the loading of non-critical components, developers can achieve faster load times, reduce bundle sizes, and enhance overall user experience.

To get the whole code, check the link below👇👇👇

In Plain English 🚀

Thank you for being a part of the In Plain English community! Before you go:

--

--