Infinite scroll in Angular Grid component

17 Sep 202520 minutes to read

The infinite scrolling feature in the Grid enables seamless handling of large data sets without compromising performance. It operates on a “load-on-demand” concept, where data is loaded as needed during scrolling. In this mode, a new block of data is fetched each time the scrollbar reaches the end of the vertical scroller, significantly improving scalability and enhancing the experience for large data collections in the Angular Grid.

A block refers to a set of rows equal to the pageSize of the Grid. If the pageSize is not explicitly defined, the Grid automatically calculates it based on the grid’s viewport height and row height.

To enable infinite scrolling, set the enableInfiniteScrolling property to true and specify a content height using the height property.

The Grid does not send a new data request when revisiting the same page in infinite scroll mode.
The height property must be defined when enabling enableInfiniteScrolling.

The following example demonstrates how to enable infinite scroll in the Grid:

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule, PageService, ToolbarService, EditService } from '@syncfusion/ej2-angular-grids'


import { Component, OnInit } from '@angular/core';
import { InfiniteScrollService } from '@syncfusion/ej2-angular-grids';
import { PageSettingsModel } from '@syncfusion/ej2-angular-grids';

const names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross', 'Geller', 'Joey', 'Bing', 'Tribbiani',
 'Janice', 'Bong', 'Perk', 'Green', 'Ken', 'Adams'];
const hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
const status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing']
const data = (count: any) => {
    const result = [];
    for (let i = 0; i < count; i++) {
        result.push({
          TaskID: i + 1,
          Engineer: names[Math.round(Math.random() * names.length)] || names[0],
          Designation: designation[Math.round(Math.random() * designation.length)] || designation[0],
          Estimation: hours[Math.round(Math.random() * hours.length)] || hours[0],
          Status: status[Math.round(Math.random() * status.length)] || status[0]
        });
    }
    return result;
};

@Component({
imports: [
        
        GridModule
    ],

providers: [PageService, ToolbarService, EditService, InfiniteScrollService],
standalone: true,
    selector: 'app-root',
    template: `<ejs-grid [dataSource]='data' height=300 [enableInfiniteScrolling]='true' [pageSettings]='options'>
                <e-columns>
                    <e-column field='TaskID' headerText='Task ID' textAlign='Right' width=70></e-column>
                    <e-column field='Engineer' width=100></e-column>
                    <e-column field='Designation' width=100></e-column>
                    <e-column field='Estimation' textAlign='Right' width=100></e-column>
                    <e-column field='Status' width=100></e-column>
                </e-columns>
                </ejs-grid>`
})
export class AppComponent implements OnInit {

    public data?: object[];
    public options?: PageSettingsModel;
    ngOnInit(): void {
        this.data = data(5000);
        this.options = { pageSize: 50 };
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Number of blocks rendered during initial loading

You can control how many blocks (pages) are initially rendered when the Grid loads. Each block contains a number of rows equal to the page size. The total number of initially rendered rows is determined by multiplying the initial block count with the page size.

Specify the initial loading page count using the infiniteScrollSettings.initialBlocks property. By default, three pages are loaded during initial rendering. Additional data is buffered based on the page size or the number of visible rows corresponding to the grid height.

The following example demonstrates how to use the initialBlocks property to set the number of initial pages loaded, controlled via a DropDownList input:

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule, PageService, ToolbarService, EditService } from '@syncfusion/ej2-angular-grids'
import { DropDownListModule } from '@syncfusion/ej2-angular-dropdowns'

import { Component, OnInit, ViewChild } from '@angular/core';
import { ChangeEventArgs } from '@syncfusion/ej2-angular-dropdowns';
import { GridComponent, InfiniteScrollService } from '@syncfusion/ej2-angular-grids';
import { PageSettingsModel } from '@syncfusion/ej2-angular-grids';

const names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross', 'Geller', 'Joey', 'Bing', 'Tribbiani',
 'Janice', 'Bong', 'Perk', 'Green', 'Ken', 'Adams'];
const hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
const status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing']
const data = (count: any) => {
    const result = [];
    for (let i = 0; i < count; i++) {
        result.push({
          TaskID: i + 1,
          Engineer: names[Math.round(Math.random() * names.length)] || names[0],
          Designation: designation[Math.round(Math.random() * designation.length)] || designation[0],
          Estimation: hours[Math.round(Math.random() * hours.length)] || hours[0],
          Status: status[Math.round(Math.random() * status.length)] || status[0]
        });
    }
    return result;
};

@Component({
imports: [
        
        GridModule,
        DropDownListModule 
        
        ],

providers: [PageService, ToolbarService, EditService, InfiniteScrollService],
standalone: true,
    selector: 'app-root',
    template: `
           <div style="display: flex">
               <label style="padding: 30px 20px 0 0" > Select initialBlocks count: :</label>
               <ejs-dropdownlist #dropdown id='value' style="padding: 26px 0 0 0" #sample index='0' 
               width='220' [dataSource]='dropDownData' (change)='valueChange($event)' >
               </ejs-dropdownlist>
            </div>
            <div style="padding: 30px 17px 0 0">
             <ejs-grid #grid [dataSource]='data' height=300 [enableInfiniteScrolling]=true [pageSettings]='options'>
                <e-columns>
                    <e-column field='TaskID' headerText='Task ID' textAlign='Right' width=70></e-column>
                    <e-column field='Engineer' width=100></e-column>
                    <e-column field='Designation' width=100></e-column>
                    <e-column field='Estimation' textAlign='Right' width=100></e-column>
                    <e-column field='Status' width=100></e-column>
                </e-columns>
                </ejs-grid>
            </div>`
})
export class AppComponent implements OnInit {

    public data?: object[];
    public options?: PageSettingsModel;
    @ViewChild('grid') public grid?: GridComponent;
    public dropDownData?: Object[] = [
        { text: 'Select count' },
        { text: '1', value: '1' },
        { text: '2', value: '2' },
        { text: '3', value: '3' },
        { text: '4', value: '4' },
        { text: '5', value: '5' },
        { text: '6', value: '6' },
        { text: '7', value: '7' }
      ];

    ngOnInit(): void {
        this.data = data(5000);
        this.options = { pageSize: 50 };
    }
    valueChange(args: ChangeEventArgs): void {
        (this.grid as GridComponent).infiniteScrollSettings.initialBlocks = parseInt((args.value as string), 10);
        (this.grid as GridComponent).refresh();    
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Efficient data caching and DOM management in grid cache mode

With grid cache mode enabled, the Grid reuses cached data blocks when revisiting them, reducing unnecessary data fetches. The DOM row elements are managed based on the value defined in the infiniteScrollSettings.maxBlocks property. When the maximum block count is exceeded, the oldest block of row elements is removed to accommodate new rows.

Enable cache mode by setting the enableCache property to true within infiniteScrollSettings.

Use the maxBlocks property in infiniteScrollSettings to set the maximum block count. The default value is 3.

The following example demonstrates enabling or disabling cache mode for infinite scrolling, triggered by the Switch component’s change event:

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule, PageService, ToolbarService, EditService } from '@syncfusion/ej2-angular-grids'
import { SwitchModule} from '@syncfusion/ej2-angular-buttons'

import { Component, OnInit, ViewChild } from '@angular/core';
import { GridComponent, InfiniteScrollService } from '@syncfusion/ej2-angular-grids';
import { PageSettingsModel } from '@syncfusion/ej2-angular-grids';
import { ChangeEventArgs } from '@syncfusion/ej2-buttons';

const names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross', 'Geller', 'Joey', 'Bing', 'Tribbiani',
 'Janice', 'Bong', 'Perk', 'Green', 'Ken', 'Adams'];
const hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
const status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing']
const data = (count: any) => {
    const result = [];
    for (let i = 0; i < count; i++) {
        result.push({
          TaskID: i + 1,
          Engineer: names[Math.round(Math.random() * names.length)] || names[0],
          Designation: designation[Math.round(Math.random() * designation.length)] || designation[0],
          Estimation: hours[Math.round(Math.random() * hours.length)] || hours[0],
          Status: status[Math.round(Math.random() * status.length)] || status[0]
        });
    }
    return result;
};

@Component({
imports: [
        
        GridModule,
        SwitchModule
        ],

providers: [PageService, ToolbarService, EditService, InfiniteScrollService],
standalone: true,
    selector: 'app-root',
    template: `
            <div style="padding: 20px 0px 20px 0px">
            <label>Enable/Disable Cache mode</label>
            <ejs-switch #switch id="switch" (change)="toggleCacheMode($event)"></ejs-switch>
            </div>
            <ejs-grid #grid [dataSource]='data' height=300 [enableInfiniteScrolling]=true  [pageSettings]='options'>
                <e-columns>
                    <e-column field='TaskID' headerText='Task ID' textAlign='Right' width=70></e-column>
                    <e-column field='Engineer' width=100></e-column>
                    <e-column field='Designation' width=100></e-column>
                    <e-column field='Estimation' textAlign='Right' width=100></e-column>
                    <e-column field='Status' width=100></e-column>
                </e-columns>
            </ejs-grid>`
})
export class AppComponent implements OnInit {

    public data?: object[];
    public options?: PageSettingsModel;
    @ViewChild('grid') public grid?: GridComponent;

    ngOnInit(): void {
        this.data = data(5000);
        this.options = { pageSize: 50 };
    }
    toggleCacheMode(args:ChangeEventArgs): void {
        (this.grid as GridComponent).infiniteScrollSettings.enableCache = args.checked;
        (this.grid as GridComponent).refresh();
      }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

When enabling Hierarchy Grid or Detail Template features together with infinite scrolling and the height property is not specified, a default height of 300px will be applied. Since the height property is required for hierarchy grid and detail template, always define it as needed.

Limitations

  • Browsers have element height limitations—extremely large record sets may be restricted based on browser capabilities.
  • The component or its parent container must have a static height set when using infinite scrolling. Setting height to 100% requires the parent container to also have a static height.
  • When infinite scrolling is enabled, copy-paste and drag-and-drop features are restricted to the rows currently visible in the viewport.
  • Cell selection does not persist when using cache mode.
  • Group records cannot be collapsed in cache mode.
  • Lazy load grouping with infinite scrolling does not support cache mode. Infinite scrolling is applied exclusively to parent-level caption rows in this context.
  • Aggregated information and total group items are shown based only on currently rendered view items. For total aggregation across all items, refer to the Group with paging guide.
  • Programmatic selection using selectRows and selectRow methods is not supported with infinite scrolling.
  • Infinite scrolling is not compatible with these features:
    1. Batch editing
    2. Normal editing
    3. Row spanning
    4. Column spanning
    5. Row template
    6. Row virtual scrolling
    7. Autofill
  • Row drag and drop limitations with infinite scrolling:
    1. In cache mode, the grid will automatically refresh if the number of row tr elements exceeds the cache limit after a drop action.
    2. When performing row drag and drop with lazy load grouping, the grid will refresh automatically.
    3. With remote data, changes from row drag and drop apply only to the UI and are lost upon grid refresh unless updated in the database. Use the rowDrop event to update the back-end accordingly and refresh the grid to display changes.

See Also