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:
- 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.
- 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).
- 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>
}
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👇👇👇
https://github.com/anthony-kigotho/news-app
CTA
Many developers and learners encounter tutorials that are either too complex or lacking in detail, making it challenging to absorb new information effectively.
Subscribe to our newsletter today, to access our comprehensive guides and tutorials designed to simplify complex topics and guide you through practical applications.
Top comments (0)