DEV Community

Yaser Adel Mehraban
Yaser Adel Mehraban

Posted on • Originally published at yashints.dev

Virtual Scrolling with Angular

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:

Virtual Scrolling only loads 5 nodes

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.

Virtual scroll network calls when scrolling

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
amineamami profile image
amineamami

great post thanks.