Virtualize large lists with the Angular CDK

Make large lists more responsive by implementing virtual scrolling.

Stephen Fluin
Stephen Fluin

The scrolling list is one of the most common UI patterns today, whether it's browsing an infinitely scrolling feed on your favorite social media site, or navigating an enterprise dashboard. When scrolling lists become very long (hundreds, thousands, or hundreds of thousands of items), application performance can suffer.

Large lists can be slow to load because the application must load and render all the data up front. They can also be slow to render and navigate because each item in the list can have rich data , media, and functionality.

Users can experience problems when they load or scroll the page, leading to frustration and page abandonment.

Virtual scrolling in Angular with the Component Dev Kit

Virtual scrolling is the primary technique used to address these scale problems. Virtual scrolling gives the impression of a very large list—by providing an appropriately sized scroll bar—and the ability to navigate the list without requiring the application to hold the entire list in memory or render it on the page.

In Angular, virtual scrolling is provided by the Component Dev Kit (CDK). By modifying the way you iterate through lists, and by supplying a couple of additional configuration parameters, the CDK's virtual scrolling will automatically manage the virtual rendering of your lists, improving page performance and responsiveness.

Instead of rendering the entire list at a time, only a subset of the items that fits on the screen (plus a small buffer) will be rendered. As the user navigates, a new subset of items is calculated and rendered, re-using the existing DOM if desired.

The rest of this post walks through how to set up basic virtual scrolling. You can see a full working example in this sample app:

Setting up virtual scrolling

First make sure you've installed @angular/cdk using your favorite package manager. To install it using npm run this command in the terminal:

npm install --save @angular/cdk

Add ScrollingModule to your app

With the CDK installed, import ScrollingModule, which handles virtual scrolling, from the @angular/cdk/scrolling package. Then add it to the imports array of your module:

import {ScrollingModule} from '@angular/cdk/scrolling';

...
imports: [
  ScrollingModule
...
]
...

Create a viewport

To see how the package works, try creating a component with a simple list of numbers from 0 to 99,999:

@Component({
  template: `<div *ngFor="let item of list">{{item}}</div>`
})
export class ScrollComponent {
  list = Array.from({length: 100000}).map((_, i) => i);
}

When the browser renders the app, it has to render 100,000 individual <div> elements. This might be fine for simple text nodes, but any complexity in the repeated template will not scale well, and any event listeners will be multiplied significantly.

To add virtual scrolling and avoid those problems, you need to create a viewport by wrapping the list in a <cdk-virtual-scroll-viewport> element:

@Component({
  template: `<cdk-virtual-scroll-viewport>
    <div *ngFor="let item of list">{{item}}</div>
    </cdk-virtual-scroll-viewport>`
})
export class ScrollComponent {
  list = Array.from({length: 100000}).map((_, i) => i);
}

Because ScrollingModule dynamically renders subsets of the list, you have to specify the height of the viewport via standard CSS. You also need to give the viewport a hint about its content by specifying the itemSize. The module uses this information to determine how many items to keep in the DOM at a given time and how to render an appropriately sized scrollbar.

@Component({
  template: `<cdk-virtual-scroll-viewport itemSize="18" style="height:80vh">
    <div *ngFor="let item of list">{{item}}</div>
    </cdk-virtual-scroll-viewport>`
})
export class ScrollComponent {
  list = Array.from({length: 100000}).map((_, i) => i);
}

Finally, convert *ngFor to *cdkVirtualFor:

@Component({
  template: `<cdk-virtual-scroll-viewport itemSize="18" style="height:80vh">
    <div *cdkVirtualFor="let item of list">{{item}}</div>
    </cdk-virtual-scroll-viewport>`
})
export class ScrollComponent {
  list = Array.from({length: 100000}).map((_, i) => i);
}

Instead of iterating through the entire list, the viewport will dynamically identify and iterate through the correct subset of the list for the user. Now when the user loads the page, the CDK should render the subset of the list that fits on the screen (plus a bit of buffer), and any scrolling events in the viewport will load and render the appropriate subset of the list:

The CDK rendering subsets of a list as the user scrolls.

Going further

The CDK's virtual scroll abilities go much further than this basic example. In the sample app, the entire list was in memory, but the list could be fetched on demand for more complex applications. You can learn more about the other capabilities of ScrollingModule and the cdkVirtualOf directive by reading about Scrolling in the CDK documentation.

Hero image by Mr Cup / Fabien Barral on Unsplash.