Angular 7 Virtual Scroll example – Angular Material CDK


The @angular/cdk/scrolling module with a technique called Virtual Scrolling helps us display a big list of elements efficiently by only rendering the items in view. Virtual Scrolling is different from infinite scroll – where it renders batches of elements and then when user goes to bottom of the list, it renders the rest. In this tutorial, we create many simple examples that show you how to work with Angular 7 Material CDK – Virtual Scrolling.

Related Post: Angular 7 Drag and Drop example – Angular Material CDK

Setup Angular 7 Material CDK

– Run cmd:
npm install @angular/cdk

– Import ScrollingModule into NgModule:

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

  declarations: [...],
  imports: [
export class AppModule { }

Angular 7 Virtual Scroll

Generate a list of 1000 random number items:

items = Array(1000).fill(0).map(() => Math.round(Math.random() * 100));
Create Viewport

We use <cdk-virtual-scroll-viewport> directive with *cdkVirtualFor inside (loop same as *ngFor).
To indicate the size of each item (it maybe height or width depending on the orientation of the Viewport), we use itemSize input property.

Item #{{i}}: {{item}}

Style with .cdk-virtual-scroll-content-wrapper (wrapper element that contains the rendered content):

.gkz-viewport {
    height: 200px;
    width: 200px;
    border: 1px solid black;
    .cdk-virtual-scroll-content-wrapper {
        display: flex;
        flex-direction: column;
    .item {
        height: 19px;
        border-bottom: 1px dashed #aaa;


Use Context Variables

With *cdkVirtualFor, we have some context variables: index, count, first, last, even, odd.

Item #{{i}}: {{item}}
count: {{count}}
first: {{first}}
last: {{last}}
even: {{even ? 'Yes' : 'No'}}
odd: {{odd ? 'Yes' : 'No'}}

With item-alternate style:

.gkz-viewport {
    .item-alternate {
        background: #ddd;


Reduce memory on Caching

By default, *cdkVirtualFor caches created items to improve rendering performance.
We can adjusted size of the view cache using templateCacheSize property (0 means disabling caching).

Item #{{i}}: {{item}}
Horizontal Viewport

By default, the orientation of Viewport is vertical, we can change it by setting orientation="horizontal".
Notice that, instead of height, itemSize property in horizontal orienttation specifies width.

Item #{{i}}: {{item}}

We also need to target CSS at .cdk-virtual-scroll-content-wrapper (the wrapper element contains the rendered content).

.gkz-horizontal-viewport {
    height: 120px;
    width: 200px;
    border: 1px solid black;
    .cdk-virtual-scroll-content-wrapper {
        display: flex;
        flex-direction: row;
    .item {
        width: 48px;
        height: 100px;
        border: 1px dashed #aaa;
        display: inline-flex;
        justify-content: center;
        align-items: center;
        writing-mode: vertical-lr;


Custom Buffer Parameters

When using itemSize directive for fixed size strategy, we can set 2 more buffer parameters: minBufferPx & maxBufferPx that calculate the extra content to be rendered beyond visible items in the Viewport:
– if the Viewport detects that there is less buffered content than minBufferPx it will immediately render more.
– the Viewport will render at least enough buffer to get back to maxBufferPx.

For example, we set: itemSize = 20, minBufferPx = 50, maxBufferPx = 200.
The viewport detects that buffer remaining is only 40px (2 items).
40px < minBufferPx: render more buffer.
– render an additional 160px (8 items) to bring the total buffer size to 40px + 160px = 200px >= maxBufferPx.


Angular 7 Virtual Scroll with Specific Data

As well as Array, *cdkVirtualFor can work with DataSource or Observable<Array>.


A DataSource is an abstract class with two methods:
connect(): is called by the Viewport to receive a stream that emits the data array.
disconnect(): will be invoked when the Viewport is destroyed.

In this example, we will create a DataSource with 1000 items, and simulate fetching data from server using setTimeout() function.

import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { DataSource, CollectionViewer } from '@angular/cdk/collections';
export class SpecDataComponent {
  dataSource = new MyDataSource();

export class MyDataSource extends DataSource {
  private PAGE_SIZE = 10;
  private fetchedPages = new Set();

  private cachedData = Array.from({ length: 1000 });
  private dataStream = new BehaviorSubject<(string | undefined)[]>(this.cachedData);

  private subscription = new Subscription();

  connect(collectionViewer: CollectionViewer): Observable<(string | undefined)[]> {
    this.subscription.add(collectionViewer.viewChange.subscribe(range => {
      const startPage = this.getPageForIndex(range.start);
      const endPage = this.getPageForIndex(range.end);
      for (let i = startPage; i <= endPage; i++) {

    return this.dataStream;

  disconnect(): void {

  private getPageForIndex(index: number): number {
    return Math.floor(index / this.PAGE_SIZE);

  private fetchPage(page: number) {
    if (this.fetchedPages.has(page)) {

    // simulate fetching data from server
    setTimeout(() => {
      this.cachedData.splice(page * this.PAGE_SIZE, this.PAGE_SIZE,
        ...Array.from({ length: this.PAGE_SIZE })
          .map((_, i) => `Item #${page * this.PAGE_SIZE + i}`));;
    }, 2000);

Iterating DataSource by *cdkVirtualFor is just like working with an Array:

{{item || 'Loading...'}}



We create an BehaviorSubject that emit an Array everytime user clicks on Button:

observableData = new BehaviorSubject([]);

emitData() {
  const items = Array(3).fill(0).map(() => Math.round(Math.random() * 100));
  const data = this.observableData.value.concat(items);;

Iterating Observable Data by *cdkVirtualFor is just like working with an Array:

Item #{{i}}: {{item}}


Angular 7 Virtual Scroll trackBy

We create an Observable data that will be changed (sorted):

import { BehaviorSubject } from 'rxjs';

interface Customer {
  id: number;
  name: string;

export class TrackbyComponent {
  customers = [
    { id: 1, name: 'John Bailey' },
    { id: 2, name: 'Amelia Kerr' },
    { id: 3, name: 'Julian Wallace' },
    { id: 4, name: 'Pippa Sutherland' },
    { id: 5, name: 'Stephanie Simpson' },

  customersObservable = new BehaviorSubject(this.customers);

  sortBy(prop: 'id' | 'name') { => ({ ...s })).sort((a, b) => {
      const aProp = a[prop], bProp = b[prop];
      if (aProp < bProp) {
        return -1;
      } else if (aProp > bProp) {
        return 1;
      return 0;

To check trackBy, we will test 3 cases:
– no trackBy function
trackBy index
trackBy name field

No trackBy function

{{}} - {{}}


With trackBy function

trackBy function works the same as the *ngFor trackBy.

indexTrackFn = (index: number) => index;
nameTrackFn = (_: number, item: Customer) =>;
trackBy index

{{}} - {{}}


trackBy a field

{{}} - {{}}


Angular 7 Virtual Scroll Strategy

Instead of using the itemSize directive on the Viewport, we can provide a custom strategy by creating a class that implements the VirtualScrollStrategy interface and providing it as the VIRTUAL_SCROLL_STRATEGY on the @Component.

import { Component, ChangeDetectionStrategy } from '@angular/core';
import { FixedSizeVirtualScrollStrategy, VIRTUAL_SCROLL_STRATEGY } from '@angular/cdk/scrolling';

export class CustomVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
  constructor() {
    super(20, 50, 200); // (itemSize, minBufferPx, maxBufferPx)

  selector: 'app-scrolling-strategy',
  templateUrl: './scrolling-strategy.component.html',
  styleUrls: ['./scrolling-strategy.component.scss'],
  providers: [{ provide: VIRTUAL_SCROLL_STRATEGY, useClass: CustomVirtualScrollStrategy }]
export class ScrollingStrategyComponent {
  items = Array(1000).fill(0).map(() => Math.round(Math.random() * 100));

In the example, our custom strategy class extends FixedSizeVirtualScrollStrategy that implements VirtualScrollStrategy.

Item #{{i}}: {{item}}


Source Code


4 thoughts on “Angular 7 Virtual Scroll example – Angular Material CDK”

  1. Wow, amazing blog layout! How long have you been blogging for?
    you made blogging look easy. The overall look of your
    website is excellent, as well as the content!

  2. Hey would you mind letting me know which web host you’re working
    with? I’ve loaded your blog in 3 different web browsers
    and I must say this blog loads a lot faster then most. Can you recommend a good hosting
    provider at a reasonable price? Kudos, I appreciate it!

Leave a Reply

Your email address will not be published. Required fields are marked *