If you have followed my series on web performance, you would've stumbled upon my image optimisation post where I went through a series of steps to lazy load images on your page.
Problem
When you have many items in your page, regardless of their nature (text, images, video, etc), it tends to slow down your page tremendously. There are ways to get around this but you should put too much effort to get it done.
When it comes to Angular, it gets even worst since this can cause really slow scrolling, plus you have to do dirty check on each of these nodes.
But fear not, I have some good news for you, since Angular V7.0.0, there is a new feature called Virtual Scroll which allows you to displays large lists of elements in a performing way by only rendering the items that fit on-screen. This may seem trivial but I am here to prove you wrong.
DOM nodes
To prove its benefits, I created an application which has a list of thousands items containing a title and an image.
Then I used the virtual scroll feature to see how many DOM
nodes are created at each time when I scroll and here is the result:
As you can see from the picture, it only loads 5 items at a time no matter where I am in the list. This is specifically good if you want to implement infinite scrolling behaviour on mobile π₯.
Network calls
To even make it better, I used the site Lorem Picsum to give each item a different image (to prevent the browser from caching the image) and since only five DOM
nodes are created at a time, the network calls are also done accordingly.
Remember we had to use the Intersection API
to achieve this. It's very convenient isn't it? π
How to do it
Ok, let's get to how to implement this. First let's create a new project using Angular CLI:
ng new virtual-scroll
With the newer versions of CLI it prompts you to specify whether you will need routing module and what is the default style file format (CSS/SCSS, etc.).
Now you will need to add the CDK
package:
npm i -s @angular/cdk
Note: You will have to navigate to the virtual-scroll folder first.
Once done open the created folder with your editor of choice #VSCode π, and open your app.module.ts
file.
Import the ScrollingModule
and ScrollDispatcher
from CDK and add them to your module:
import {
ScrollingModule,
ScrollDispatcher,
} from '@angular/cdk/scrolling';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
ScrollingModule,
MatListModule,
MatToolbarModule,
MatCardModule,
],
providers: [ScrollDispatcher],
bootstrap: [AppComponent],
})
export class AppModule {}
Note: I am using Material Design and that's why I have more imports.
Now open your app.component.ts
file (feel free to create a new component if you like, I am just hacking something together π€·β) and create an array of 1000 items containing a title and an image:
import { Component } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
interface IImage {
title: string;
src: string;
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
images: IImage[] = Array.from(
new Array(1000),
(x, i) => ({
title: `Image #${i}`,
src: `https://picsum.photos/200/?${i}`,
})
);
observableImages = new BehaviorSubject<
IImage[]
>(this.images);
}
I am using a subject behavior from RxJs
just to simulate having an observable and loading data asynchronously from server.
Now in the app.component.html
add we need to add the cdk-virtual-scroll-viewport
and give it an itemSize
which has a pixel unit.
This is basically where everything is glued together.
When all items are the same fixed size (in this case all cards have the same height), you can use the FixedSizeVirtualScrollStrategy
. This can be easily added to your viewport using the itemSize
directive. The advantage of this constraint is that it allows for better performance, since items do not need to be measured as they are rendered.
<cdk-virtual-scroll-viewport class="list-container lg" [itemSize]="200">
<div *cdkVirtualFor="let image of observableImages | async;">
<mat-card class="example-card">
<mat-card-header>
<div
mat-card-avatar
class="example-header-image"
></div>
<mat-card-title
>{{image.title}}</mat-card-title
>
<mat-card-subtitle>WoW</mat-card-subtitle>
</mat-card-header>
<img
mat-card-image
[src]="image.src"
alt="Random photo"
/>
<mat-card-content>
<p>
This is a random image selected from
LoremPicsum.
</p>
</mat-card-content>
<mat-card-actions>
<button mat-button>LIKE</button>
<button mat-button>SHARE</button>
</mat-card-actions>
</mat-card>
</div>
</cdk-virtual-scroll-viewport>
All I have here is a container with 200px
as itemSize
. Inside I am creating a div
in a loop over my list asynchronously and giving it a title, and an image.
The HTML
for card is from Angular Material examples.
And that's it. Now run ng serve
in a VS Code terminal and open up a browser and navigate to localhost:4200
.
Voila
And that's it. Look at how easy it is to implement a lazy loading strategy for items in a list in Angular with their new Virtual Scroll
feature with so little code required.
You can read more about this feature on Angular Material website and the code for this example is available on my GitHub repository.
Top comments (1)
great post thanks.