Infinite scrolling in Vue Grid component

23 Jul 202424 minutes to read

The infinite scrolling feature in the Grid is a powerful tool for seamlessly handling extensive data sets without compromising grid performance. It operates on a “load-on-demand” concept, ensuring that data is fetched only when needed. In the default infinite scrolling mode, a new block of data is loaded each time the scrollbar reaches the end of the vertical scroller. This approach significantly enhances the user experience when working with large data collections in the Vue Grid.

In this mode, a block of data accumulates every time the scrollbar reaches the end of the scroller. To clarify, in this context, a block represents the pageSize of the Grid. If the pageSize is not explicitly specified, the Grid will automatically calculate it based on the grid viewport height and row height.

To enable infinite scrolling, you need to define enableInfiniteScrolling as true and content height by height property.

In this feature, the Grid will not initiate a new data request when revisiting the same page.
The height property must be specified when enabling enableInfiniteScrolling.

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

<template>
    <div id="app">
        <ejs-grid :dataSource='gridData' 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>
</template>

<script setup>
import { provide } from "vue";
import { GridComponent as EjsGrid, ColumnDirective as EColumn, ColumnsDirective as EColumns, InfiniteScroll,Page} from "@syncfusion/ej2-vue-grids";
let names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross', 'Geller', 'Joey', 'Bing', 'Tribbiani', 'Janice', 'Bong', 'Perk', 'Green', 'Ken', 'Adams'];
let hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
let status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing'];

let data = (count) => {
  let result = [];
  for (let i = 0; i < count; i++) {
    result.push({
      TaskID: i + 1,
      Engineer: names[Math.floor(Math.random() * names.length)],
      Designation: designation[Math.floor(Math.random() * designation.length)],
      Estimation: hours[Math.floor(Math.random() * hours.length)],
      Status: status[Math.floor(Math.random() * status.length)]
    });
  }
  return result;
};

const gridData = data(1000);
const options = { pageSize: 50 };
provide('grid', [Page,InfiniteScroll]);
</script>

<style>
@import "../node_modules/@syncfusion/ej2-base/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-buttons/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-calendars/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-dropdowns/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-inputs/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-popups/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-vue-grids/styles/tailwind.css";
</style>
<template>
    <div id="app">
        <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>
    </div>
</template>
<script>
import { GridComponent, ColumnsDirective, ColumnDirective, InfiniteScroll} from "@syncfusion/ej2-vue-grids";
let names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross', 'Geller', 'Joey', 'Bing', 'Tribbiani', 'Janice', 'Bong', 'Perk', 'Green', 'Ken', 'Adams'];
let hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
let status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing']
let data = (count) => {
    let 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;
};
export default {
name: "App",
components: {
"ejs-grid":GridComponent,
"e-columns":ColumnsDirective,
"e-column":ColumnDirective
},
  data() {
    return {
      data: data(1000),
      options: { pageSize: 50 }
    };
  },
  provide: {
      grid: [InfiniteScroll]
  }
}
</script>
<style>
 @import "../node_modules/@syncfusion/ej2-base/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-buttons/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-calendars/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-dropdowns/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-inputs/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-popups/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-vue-grids/styles/tailwind.css";
</style>

Number of blocks rendered during initial loading

The number of blocks to be initially rendered when the Grid is loaded. Each block corresponds to a page size of the Grid, resulting in the rendering of a certain number of row elements determined by multiplying the initial block size with the page size.

You can define the initial loading pages count by using infiniteScrollSettings.initialBlocks property . By default, this property loads three pages during the initial rendering. Subsequently, additional data is buffered and loaded based on either the page size or the number of rows rendered within the provided height.

The following an example of how you can use the initialBlocks property to set the initial loading pages based on DropDownList input:

<template>
    <div id="app">
      <div style="display:inline-block">
        <label style="padding: 30px 20px 0 0" > Select initialBlocks count:</label>
        <ejs-dropdownlist ref="dropdown" id='value' :index='2' 
        width='220' :dataSource='dropDownData' @change='valueChange' >
        </ejs-dropdownlist>
      </div>
      <div style="padding: 30px 17px 0 0">
        <ejs-grid ref="grid" :dataSource='griddata' height=300 id="grid" :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>
    </div>
</template>
<script setup>
import { ref, provide } from "vue";
import { DropDownListComponent as EjsDropdownlist } from "@syncfusion/ej2-vue-dropdowns";
import { GridComponent as EjsGrid, ColumnDirective as EColumn, ColumnsDirective as EColumns, InfiniteScroll,Page } from "@syncfusion/ej2-vue-grids";
let names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross', 'Geller', 'Joey', 'Bing', 'Tribbiani', 'Janice', 'Bong', 'Perk', 'Green', 'Ken', 'Adams'];
let hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
let status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing']
let generateData  = (count) => {
  let 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;
};
const griddata = generateData (1000);
const options = { pageSize: 50 };
const dropDownData= [
        { 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' }
      ];
const grid=ref(null);
const valueChange=function(args) {
      if (args.itemData && args.itemData.value) {
       grid.value.ej2Instances.infiniteScrollSettings.initialBlocks = parseInt(args.itemData.value, 10);
        grid.value.ej2Instances.refresh();   
      }
    }
provide('grid', [Page,InfiniteScroll]);
</script>
<style>
  @import "../node_modules/@syncfusion/ej2-base/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-buttons/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-calendars/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-dropdowns/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-inputs/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-popups/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-vue-grids/styles/tailwind.css";
</style>
<template>
    <div id="app">
      <div style="display:inline-block">
        <label style="padding: 30px 20px 0 0" > Select initialBlocks count:</label>
        <ejs-dropdownlist ref="dropdown" id='value' :index='2' 
        width='220' :dataSource='dropDownData' @change='valueChange' >
        </ejs-dropdownlist>
      </div>
      <div style="padding: 30px 17px 0 0">
        <ejs-grid ref="grid" :dataSource='griddata' height=300 id="grid" :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>
    </div>
</template>
<script>
import { GridComponent, ColumnsDirective, ColumnDirective, InfiniteScroll } from "@syncfusion/ej2-vue-grids";
import { DropDownListComponent } from "@syncfusion/ej2-vue-dropdowns";
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 designations = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
const statuses = ['Completed', 'Open', 'In Progress', 'Review', 'Testing'];

const generateData = (count) => {
  let result = [];
  for (let i = 0; i < count; i++) {
    result.push({
      TaskID: i + 1,
      Engineer: names[Math.floor(Math.random() * names.length)],
      Designation: designations[Math.floor(Math.random() * designations.length)],
      Estimation: hours[Math.floor(Math.random() * hours.length)],
      Status: statuses[Math.floor(Math.random() * statuses.length)]
    });
  }
  return result;
};

export default {
  name: "App",
  components: {
    "ejs-grid": GridComponent,
    "e-columns": ColumnsDirective,
    "e-column": ColumnDirective,
    'ejs-dropdownlist': DropDownListComponent,
  },
  data() {
    return {
      griddata: generateData(1000),
      options: { pageSize: 50 },
      dropDownData: [
        { 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' }
      ]
    };
  },
  methods: {
    valueChange(args) {
      if (args.itemData && args.itemData.value) {
        this.$refs.grid.ej2Instances.infiniteScrollSettings.initialBlocks = parseInt(args.itemData.value, 10);
        this.$refs.grid.ej2Instances.refresh();   
      }
    }
  },
  provide: {
    grid: [InfiniteScroll]
  }
}
</script>

<style>
@import "../node_modules/@syncfusion/ej2-base/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-buttons/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-calendars/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-dropdowns/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-inputs/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-popups/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-vue-grids/styles/tailwind.css";
</style>

Efficient data caching and DOM management in grid cache mode

In Grid cache mode, cached data blocks are reused when revisiting them, reducing the need for frequent data requests while navigating the same block. This mode also manages DOM row elements based on the infiniteScrollSettings.maxBlocks count value. If this limit is exceeded, it removes a block of row elements to create new rows.

To enable cache mode, you need to define enableCache property of infiniteScrollSettings as true.

To enable maximum blocks, you need to define maxBlocks count of infiniteScrollSettings, By default this property value is 3.

The following example that demonstrates how to enable/disable cache mode in infinite scrolling of the grid based on a Switch componentchange event :

<template>
  <div id="app">
    <div style="padding: 20px 0px 20px 0px;display:flex">
      <label style="padding: 0px 20px 0px 0px;font-weight: bold">Enable/Disable Cache mode</label>
      <ejs-switch ref="switch" id="switch" :change="toggleCacheMode"></ejs-switch>
    </div>
    <div style="padding: 30px 17px 0 0">
      <ejs-grid ref="grid" :dataSource="gridData" 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>
  </div>
</template>

<script setup>
import { ref, provide } from "vue";
import { GridComponent as EjsGrid, ColumnDirective as EColumn, ColumnsDirective as EColumns, InfiniteScroll,Page } from "@syncfusion/ej2-vue-grids";
import { SwitchComponent as EjsSwitch } from "@syncfusion/ej2-vue-buttons";

let names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross', 'Geller', 'Joey', 'Bing', 'Tribbiani', 'Janice', 'Bong', 'Perk', 'Green', 'Ken', 'Adams'];
let hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
let status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing'];

let data = (count) => {
  let result = [];
  for (let i = 0; i < count; i++) {
    result.push({
      TaskID: i + 1,
      Engineer: names[Math.floor(Math.random() * names.length)],
      Designation: designation[Math.floor(Math.random() * designation.length)],
      Estimation: hours[Math.floor(Math.random() * hours.length)],
      Status: status[Math.floor(Math.random() * status.length)]
    });
  }
  return result;
};

const gridData = data(1000);
const options = { pageSize: 50 };
const grid = ref(null);

const toggleCacheMode = function(args) {
  if (grid.value && grid.value.ej2Instances) {
    grid.value.ej2Instances.infiniteScrollSettings.enableCache = args.checked;
    grid.value.ej2Instances.refresh(); 
  }
}
provide('grid', [Page,InfiniteScroll]);
</script>

<style>
@import "../node_modules/@syncfusion/ej2-base/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-buttons/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-calendars/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-dropdowns/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-inputs/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-popups/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind.css";
@import "../node_modules/@syncfusion/ej2-vue-grids/styles/tailwind.css";
</style>
<template>
    <div id="app">
      <div style="padding: 20px 0px 20px 0px;display:flex">
        <label style="padding: 0px 20px 0px 0px;font-weight: bold">Enable/Disable Cache mode</label>
        <ejs-switch ref="switch" id="switch" :change="toggleCacheMode"></ejs-switch>
      </div>
      <div style="padding: 30px 17px 0 0">
        <ejs-grid ref="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>
    </div>
</template>
<script>
import { GridComponent, ColumnsDirective, ColumnDirective, InfiniteScroll} from "@syncfusion/ej2-vue-grids";
import { SwitchComponent } from "@syncfusion/ej2-vue-buttons";
let names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross', 'Geller', 'Joey', 'Bing', 'Tribbiani', 'Janice', 'Bong', 'Perk', 'Green', 'Ken', 'Adams'];
let hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
let status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing']
let data = (count) => {
    let 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;
};
export default {
name: "App",
components: {
"ejs-grid":GridComponent,
"e-columns":ColumnsDirective,
"e-column":ColumnDirective,
 "ejs-switch": SwitchComponent
},
  data() {
    return {
      data: data(1000),
      options: { pageSize: 50 },
    };
  },
   methods: {
   toggleCacheMode(args){
      this.$refs.grid.ej2Instances.infiniteScrollSettings.enableCache = args.checked;
      this.$refs.grid.ej2Instances.refresh();   
    }
   },
  provide: {
      grid: [InfiniteScroll]
  }
}
</script>
<style>
  @import "../node_modules/@syncfusion/ej2-base/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-buttons/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-calendars/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-dropdowns/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-inputs/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-popups/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind.css";
  @import "../node_modules/@syncfusion/ej2-vue-grids/styles/tailwind.css";
</style>

Limitations

  • Due to the element height limitation in browsers, the maximum number of records loaded by the grid is limited due to the browser capability.
  • It is necessary to set a static height for the component or its parent container when using infinite scrolling. The 100% height will work only if the component height is set to 100%, and its parent container has a static height.
  • When infinite scrolling is activated, compatibility for copy-paste and drag-and-drop operations is limited to the data items visible in the current viewport of the grid.
  • Cell selection will not be persisted in cache mode.
  • The group records cannot be collapsed in cache mode.
  • Lazy load grouping with infinite scrolling does not support cache mode, and the infinite scrolling mode is exclusively applicable to parent-level caption rows in this scenario.
  • The aggregated information and total group items are displayed based on the current view items. To get these information regardless of the view items, refer to the Group with paging topic.
  • InfiniteScrolling support for Normal and Dialog mode editing, so this feature is not compatible with AutoFill.
  • Programmatic selection using the selectRows and selectRow method is not supported in infinite scrolling.
  • Infinite scrolling is not compatible with the following features:
    1. Batch editing
    2. Normal editing
    3. Row spanning
    4. Column spanning
    5. Row template
    6. Row virtual scrolling
    7. Detail template
    8. Hierarchy features
    9. Autofill
    10. Page
  • Limitations of row drag and drop with infinite scrolling
    1. In cache mode, the grid refreshes automatically if the content’s tr element count exceeds the cache limit of the grid’s content after the drop action.
    2. When performing row drag and drop with lazy load grouping, the grid will refresh automatically.
    3. In remote data, changes are applied only in the UI. They will be lost once the grid is refreshed. To restore them, you need to update the changes in your database. By using the rowDrop event, you can send the request to the server and apply the changes in your database. After this, you need to refresh the grid to show the updated data.

See Also