Virtual scrolling in Angular Pivot Table component

27 Aug 202520 minutes to read

Virtual Scrolling

Virtual scrolling enables efficient handling of large datasets by rendering only the rows and columns visible in the current viewport. This approach prevents performance degradation when working with substantial amounts of data, as content refreshes dynamically during vertical or horizontal scrolling. This feature can be enabled by setting the enableVirtualization property to true.

To use the virtual scrolling feature, inject the VirtualScrollService module in the @NgModule.providers section.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { PivotViewAllModule, PivotFieldListAllModule } from '@syncfusion/ej2-angular-pivotview'



import { Component, OnInit } from '@angular/core';
import { IDataSet, PivotView, VirtualScrollService } from '@syncfusion/ej2-angular-pivotview';
import { Pivot_Data } from './datasource';
import { DataSourceSettingsModel } from '@syncfusion/ej2-pivotview/src/model/datasourcesettings-model';

@Component({
imports: [
        
        PivotViewAllModule,
        PivotFieldListAllModule
    ],


standalone: true,
  selector: 'app-container',
  providers: [VirtualScrollService],
  // specifies the template string for the pivot table component
  template: `<ejs-pivotview #pivotview id='PivotView' [dataSourceSettings]=dataSourceSettings enableVirtualization='true' height='350'></ejs-pivotview>`
})

export class AppComponent implements OnInit  {
public dataSourceSettings?: DataSourceSettingsModel;
public date1?: number;
public date2?: number;
data(count: number) {
  let result: Object[] = [];
  let dt: number = 0;
  for (let i: number = 1; i < count + 1; i++) {
    dt++;
    let round: string;
    let toString: string = i.toString();
    if (toString.length === 1) {
      round = "0000" + i;
    } else if (toString.length === 2) {
      round = "000" + i;
    } else if (toString.length === 3) {
      round = "00" + i;
    } else if (toString.length === 4) {
      round = "0" + i;
    } else {
      round = toString;
    }
    result.push({
      ProductID: "PRO-" + round,
      Year: "FY " + (dt + 2013),
      Price: Math.round(Math.random() * 5000) + 5000,
      Sold: Math.round(Math.random() * 80) + 10
    });
    if (dt / 4 == 1) {
        dt = 0;
    }
  }
  return result;
}
    ngOnInit(): void {
        this.dataSourceSettings = {
        dataSource: this.data(1000) as IDataSet[],
        enableSorting: false,
        expandAll: true,
        formatSettings: [{ name: 'Price', format: 'C0' }],
        rows: [{ name: 'ProductID' }],
        columns: [{ name: 'Year' }],
        values: [{ name: 'Price', caption: 'Unit Price' }, { name: 'Sold', caption: 'Unit Sold' }]
        };
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Virtual scrolling with single page mode

When virtual scrolling is enabled, the Pivot Table renders not only the current view page but also the adjacent previous and next pages by default. While this approach supports smooth navigation, it can increase computational load and reduce performance when working with extensive datasets, as additional rows and columns from surrounding pages are processed.

To optimize performance, set the allowSinglePage property to true within the virtualScrollSettings. Enabling this property ensures that only the rows and columns for the current view page are rendered during virtual scrolling. This significantly enhances the performance of the Pivot Table, especially during initial rendering and user actions such as drilling up, drilling down, sorting, filtering, and more.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { PivotViewAllModule } from '@syncfusion/ej2-angular-pivotview'

import { Component, OnInit } from '@angular/core';
import { IDataSet, VirtualScrollService, VirtualScrollSettingsModel } from '@syncfusion/ej2-angular-pivotview';
import { DataSourceSettingsModel } from '@syncfusion/ej2-pivotview/src/model/datasourcesettings-model';

@Component({
imports: [
        
        PivotViewAllModule
    ],


standalone: true,
  selector: 'app-container',
  providers: [VirtualScrollService],
  template: `<ejs-pivotview #pivotview id='PivotView' width='100%' height='350'
    [dataSourceSettings]='dataSourceSettings' enableVirtualization='true' [virtualScrollSettings]='virtualScrollSettings'>
  </ejs-pivotview>`
})

export class AppComponent implements OnInit  {
  public dataSourceSettings?: DataSourceSettingsModel;
  public virtualScrollSettings?: VirtualScrollSettingsModel;

  data(count: number) {
    let result: Object[] = [];
    let dt: number = 0;
    for (let i: number = 1; i < count + 1; i++) {
      dt++;
      let round: string;
      let toString: string = i.toString();
      if (toString.length === 1) {
        round = "0000" + i;
      } else if (toString.length === 2) {
        round = "000" + i;
      } else if (toString.length === 3) {
        round = "00" + i;
      } else if (toString.length === 4) {
        round = "0" + i;
      } else {
        round = toString;
      }
      result.push({
        ProductID: "PRO-" + round,
        Year: "FY " + (dt + 2013),
        Sold: Math.round(Math.random() * 80) + 10
      });
      if (dt / 4 == 1) {
        dt = 0;
      }
    }
    return result;
  }

  ngOnInit(): void {
    this.virtualScrollSettings = {
      allowSinglePage: true
    } as VirtualScrollSettingsModel;

    this.dataSourceSettings = {
      dataSource: this.data(1000) as IDataSet[],
      enableSorting: false,
      expandAll: true,
      formatSettings: [{ name: 'Price', format: 'C0' }],
      rows: [{ name: 'ProductID' }],
      columns: [{ name: 'Year' }],
      values: [{ name: 'Price', caption: 'Unit Price' },
        { name: 'Sold', caption: 'Unit Sold' }
      ]
    };
  }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Limitations for virtual scrolling

  • In virtual scrolling, the columnWidth property in gridSettings should be in pixels, and percentage values are not accepted.
  • Features such as auto fit, column resizing, text wrapping, and setting specific column widths through events can dynamically affect the row height and column width in the pivot table at runtime. However, these changes are not considered in the scroller calculations, particularly with large datasets. This can lead to performance issues and problems with UI functionality during scrolling. Therefore, it is not recommended to use these features alongside virtualization in the pivot table.
  • Grouping, which takes additional time to splitting the raw items into the provided format.
  • Date Formatting, which takes additional time to convert date format.
  • Date Formatting with sorting, here additionally full date time format should be framed to perform sorting along with the provided date format which lags the performance.
  • When using OLAP data, subtotals and grand totals are only displayed when measures are bound at the last position in the rows or columns axis. Otherwise, the data from the pivot table will be shown without summary totals.
  • Even if virtual scrolling is enabled, not only is the current view port data retrieved, but also the data for the immediate previous page and the immediate next page. As a result, when the end user scrolls slightly ahead or behind, the next or previous page data is displayed immediately without requiring a refresh. Note: If the pivot table’s width and height are large, the loading data count in the current, previous, and next viewport (pages) will also increase, affecting performance.

Virtual scrolling for static field list

Virtual scrolling works automatically with “Popup” field lists when you set the enableVirtualization property in the Pivot Table to true. However, when using a static field list (displayed as a separate component), you need to connect both components manually.

Here’s how to make virtual scrolling work with a static field list:

  1. Enable virtual scrolling in the PivotTable component by setting the enableVirtualization property to true, which improves performance for large datasets.
  2. Connect the PivotFieldList component to the PivotTable component using the load event.
  3. Ensure synchronization between the pivot table and field list by updating the pivot table’s report configuration with the field list’s report configuration during the load event.
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { PivotViewAllModule, PivotFieldListAllModule } from '@syncfusion/ej2-angular-pivotview'



import { Component, ViewChild } from '@angular/core';
import { PivotFieldListComponent, PivotViewComponent, FieldListService, IDataSet,
    EnginePopulatedEventArgs, VirtualScrollService, PivotView } from '@syncfusion/ej2-angular-pivotview';
import { Browser, setStyleAttribute, prepend } from '@syncfusion/ej2-base';
import { DataSourceSettingsModel } from '@syncfusion/ej2-pivotview/src/model/datasourcesettings-model';

@Component({
imports: [
        
        PivotViewAllModule,
        PivotFieldListAllModule
    ],


standalone: true,
  selector: 'app-container',
  providers: [FieldListService, VirtualScrollService],
  styleUrls: ['./app.component.css'],
  template: `<ejs-pivotfieldlist #pivotfieldlist id='PivotFieldList' [dataSourceSettings]=dataSourceSettings renderMode="Fixed" (enginePopulated)='afterPopulate($event)' allowCalculatedField='true' (load)='onLoad()' (dataBound)='ondataBound()'></ejs-pivotfieldlist>
  <ejs-pivotview #pivotview id='PivotViewFieldList' width='99%' height='530' enableVirtualization='true '(enginePopulated)='afterEnginePopulate($event)'></ejs-pivotview>`
})

export class AppComponent {
    public dataSourceSettings?: DataSourceSettingsModel;
    data(count: number) {
      let result: Object[] = [];
      let dt: number = 0;
      for (let i: number = 1; i < count + 1; i++) {
        dt++;
        let round: string;
        let toString: string = i.toString();
        if (toString.length === 1) {
          round = "0000" + i;
        } else if (toString.length === 2) {
          round = "000" + i;
        } else if (toString.length === 3) {
          round = "00" + i;
        } else if (toString.length === 4) {
          round = "0" + i;
        } else {
          round = toString;
        }
        result.push({
          ProductID: "PRO-" + round,
          Year: "FY " + (dt + 2013),
          Price: Math.round(Math.random() * 5000) + 5000,
          Sold: Math.round(Math.random() * 80) + 10
        });
        if (dt / 4 == 1) {
          dt = 0;
        }
      }
      return result;
    }

    @ViewChild('pivotview', {static: false})
    public pivotObj?: PivotViewComponent;

    @ViewChild('pivotfieldlist')
    public fieldListObj?: PivotFieldListComponent;

    afterPopulate(arge: EnginePopulatedEventArgs): void {
      if (this.fieldListObj && this.pivotObj) {
          this.fieldListObj.updateView(this.pivotObj);
      }
    }
    afterEnginePopulate(args: EnginePopulatedEventArgs): void {
      if (this.fieldListObj && this.pivotObj) {
          this.fieldListObj.update(this.pivotObj);
      }
    }
    onLoad(): void {
      if (Browser.isDevice) {
        (this.fieldListObj as PivotFieldListComponent).renderMode = 'Popup';
          (this.fieldListObj as PivotFieldListComponent).target = '.control-section';
          (document.getElementById('PivotFieldList') as HTMLElement).removeAttribute('style');
          setStyleAttribute(document.getElementById('PivotFieldList') as HTMLElement, {
              'height': 0,
              'float': 'left'
          });
      }
      (this.fieldListObj as PivotFieldListComponent).pivotGridModule = this.pivotObj as PivotViewComponent;
      //Assigning report to pivot table component
      (this.pivotObj as PivotViewComponent).dataSourceSettings = (this.fieldListObj as PivotFieldListComponent).dataSourceSettings;
      //Generating page settings based on pivot table component’s size.
      (this.pivotObj as PivotViewComponent).updatePageSettings(true);
      //Assigning page settings to field list component.
      (this.fieldListObj as PivotFieldListComponent).pageSettings = (this.pivotObj as PivotViewComponent).pageSettings;
    }

    ondataBound(): void {
      if (Browser.isDevice) {
        prepend([document.getElementById('PivotFieldList') as HTMLElement], document.getElementById('PivotView') as HTMLElement);
      }
    }

    ngOnInit(): void {
      this.dataSourceSettings = {
          dataSource: this.data(1000) as IDataSet[],
          enableSorting: false,
          expandAll: true,
          formatSettings: [{ name: 'Price', format: 'C0' }],
          rows: [{ name: 'ProductID' }],
          columns: [{ name: 'Year' }],
          values: [{ name: 'Price', caption: 'Unit Price' }, { name: 'Sold', caption: 'Unit Sold' }]
      };
    }
 }
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

See also