All docs
V20.2
24.1
23.2
23.1
22.2
22.1
21.2
21.1
20.2
20.1
19.2
The page you are viewing does not exist in version 19.2.
19.1
The page you are viewing does not exist in version 19.1.
18.2
The page you are viewing does not exist in version 18.2.
18.1
The page you are viewing does not exist in version 18.1.
17.2
The page you are viewing does not exist in version 17.2.
A newer version of this page is available. Switch to the current version.

jQuery DataGrid - Enhance Performance on Large Datasets

Remote Operations

We recommend server-side data processing for large datasets. The ODataStore supports server-side paging, filtering, and sorting. DevExtreme provides extensions that help implement data processing for ASP.NET and PHP servers. You can also use the third-party extension for MongoDB. If these extensions do not suit your data source, implement server-side data processing manually according to the protocol described in the Custom Sources article.

Specify the remoteOperations property to notify the DataGrid of the server's data processing operations.

jQuery
JavaScript
$(function() {
    $("#dataGridContainer").dxDataGrid({ 
        // ...
        remoteOperations: {
            filtering: true,
            paging: true,
            sorting: true,
            groupPaging: true,
            grouping: true,
            summary: true
        }
    });
}); 
Angular
HTML
TypeScript
<dx-data-grid ... >
    <dxo-remote-operations
        [filtering]="true"
        [paging]="true"
        [sorting]="true"
        [summary]="true"
        [grouping]="true"
        [groupPaging]="true"> 
    </dxo-remote-operations>
</dx-data-grid>
import { DxDataGridModule } from "devextreme-angular";
// ...
export class AppComponent {
    // ...
}
@NgModule({
    imports: [
        // ...
        DxDataGridModule
    ],
    // ...
})
Vue
App.vue
<template>
    <DxDataGrid ... >
        <DxRemoteOperations
            :filtering="true"
            :paging="true"
            :sorting="true"
            :summary="true"
            :grouping="true"
            :group-paging="true"
        />
    </DxDataGrid>
</template>

<script>
import 'devextreme/dist/css/dx.common.css';
import 'devextreme/dist/css/dx.light.css';

import DxDataGrid, {
    DxRemoteOperations
} from 'devextreme-vue/data-grid';

export default {
    components: {
        DxDataGrid,
        DxRemoteOperations
    },
    // ...
}
</script>
React
App.js
import React from 'react';

import 'devextreme/dist/css/dx.common.css';
import 'devextreme/dist/css/dx.light.css';

import DataGrid, {
    RemoteOperations
} from 'devextreme-react/data-grid';

class App extends React.Component {
    render() {
        return (
            <DataGrid ... >
                <RemoteOperations
                    filtering={true}
                    paging={true}
                    sorting={true}
                    summary={true}
                    grouping={true}
                    groupPaging={true}
                />
            </DataGrid>
        );
    }
}
export default App;

Deferred Selection

Use deferred mode to increase the DataGrid's performance when selecting multiple rows at once. In this mode, only the API (for example, the getSelectedRowsData() or getSelectedRowKeys() method) can request the DataGrid data. Assign true to the selection.deferred property to use deferred selection.

jQuery
JavaScript
$(function(){
    $("#dataGridContainer").dxDataGrid({
        // ...
        dataSource: {
            store: {
                type: "odata",
                url: "https://js.devexpress.com/Demos/DevAV/odata/Products",
                key: "Product_ID"
            }
        },
        selection: {
            mode: "multiple",
            allowSelectAll: true,
            deferred: true
        }
    }); 
});
Angular
HTML
TypeScript
<dx-data-grid
    [dataSource]="dataSource">
    <dxo-selection
        mode="multiple"
        [allowSelectAll]="true"
        [deferred]="true">
    </dxo-selection>
</dx-data-grid>
import { DxDataGridModule } from "devextreme-angular";
import "devextreme/data/odata/store";
// ...
export class AppComponent {
    dataSource = {
        store: {
            type: "odata",
            url: "https://js.devexpress.com/Demos/DevAV/odata/Products",
            key: "Product_ID"
        }
    }
}
@NgModule({
    imports: [
        // ...
        DxDataGridModule
    ],
    // ...
})
Vue
App.vue
<template>
    <DxDataGrid :data-source="store">
        <DxSelection
            mode="multiple"
            :allow-select-all="true"
            :deferred="true"
        />
    </DxDataGrid>
</template>

<script>
import 'devextreme/dist/css/dx.common.css';
import 'devextreme/dist/css/dx.light.css';

import DxDataGrid, {
    DxSelection
} from 'devextreme-vue/data-grid';

import ODataStore from 'devextreme/data/odata/store';

const store = new ODataStore({
    url: 'https://js.devexpress.com/Demos/DevAV/odata/Products',
    key: 'Product_ID'
});

export default {
    components: {
        DxDataGrid,
        DxSelection
    },
    data() {
        return {
            store
        }
    }
}
</script>
React
App.js
import React from 'react';

import 'devextreme/dist/css/dx.common.css';
import 'devextreme/dist/css/dx.light.css';

import DataGrid, {
    Selection
} from 'devextreme-react/data-grid';

import ODataStore from 'devextreme/data/odata/store';

const store = new ODataStore({
    url: 'https://js.devexpress.com/Demos/DevAV/odata/Products',
    key: 'Product_ID'
});

class App extends React.Component {
    render() {
        return (
            <DataGrid dataSource={store}>
                <Selection
                    mode="multiple"
                    allowSelectAll={true}
                    deferred={true}
                />
            </DataGrid>
        );
    }
}
export default App;
NOTE
You should specify the key property of the Store underlying the dataSource to ensure that deferred selection works properly.

The following tasks require using different API in deferred mode:

  • Setting initial selection

    You should use the selectionFilter instead of the selectedRowKeys property to set the initially selected rows in deferred mode. Pass a filter expression to define records that should be selected.

    jQuery
    JavaScript
    $(function () {
        $("#dataGridContainer").dxDataGrid({
            // ...
            selectionFilter: ["Task_Status", "=", "Completed"]
        });
    });
    Angular
    HTML
    TypeScript
    <dx-data-grid ...
        [selectionFilter]="['Task_Status', '=', 'Completed']">
    </dx-data-grid>
    import { DxDataGridModule } from "devextreme-angular";
    // ...
    export class AppComponent {
        // ...
    }
    @NgModule({
        imports: [
            // ...
            DxDataGridModule
        ],
        // ...
    })
    Vue
    App.vue
    <template>
        <DxDataGrid ...
            :selection-filter="['Task_Status', '=', 'Completed']">
        </DxDataGrid>
    </template>
    
    <script>
    // ...
    </script>
    React
    App.js
    // ...
    class App extends React.Component {
        selectionFilter = ['Task_Status', '=', 'Completed'];
    
    
    render() {
        return (
            &lt;DataGrid ...
                defaultSelectionFilter={this.selectionFilter}&gt;
            &lt;/DataGrid&gt;
        );
    }
    } export default App;

    The DataGrid changes the selectionFilter property's value internally when a user selects a row. You can use the following code to obtain this property's value and send it to the server:

    jQuery
    JavaScript
    function sendSelectedRows() {
        var selectionFilter = dataGridInstance.option("selectionFilter");
        $.ajax({
            method: "POST",
            url: "url/to/data/processing/method",
            data: { 
                filter: (selectionFilter ? JSON.stringify(selectionFilter) : null)
            }
        });
    }
    Angular
    TypeScript
    import { ..., ViewChild } from "@angular/core";
    import { HttpClient, HttpClientModule } from "@angular/common/http";
    import { DxDataGridModule, DxDataGridComponent } from "devextreme-angular";
    // ...
    export class AppComponent {
        @ViewChild(DxDataGridComponent, { static: false }) dataGrid: DxDataGridComponent;
        // Prior to Angular 8
        // @ViewChild(DxDataGridComponent) dataGrid: DxDataGridComponent;
        constructor(private httpClient: HttpClient) { }
        sendSelectedRows() {
            var selectionFilter = this.dataGrid.instance.option("selectionFilter");
            this.httpClient
                .post("url/to/data/processing/method",  
                "filter: " + (selectionFilter ? JSON.stringify(selectionFilter) : null)
                )
                .subscribe();
        }
    }
    @NgModule({
        imports: [
            // ...
            DxDataGridModule,
            HttpClientModule
        ],
        // ...
    })
    Vue
    App.vue
    <template>
        <DxDataGrid ...
            v-model:selection-filter="selectionFilter">
        </DxDataGrid>
    </template>
    
    <script>
    import 'whatwg-fetch';
    // ...
    export default {
        // ...
        data() {
            return {
                selectionFilter: ['Task_Status', '=', 'Completed']
            }
        },
        methods: {
            sendSelectedRows() {
                return new Promise((resolve, reject) => {
                    fetch('https://mydomain.com/MyDataService', {
                        method: 'POST',
                        body: JSON.stringify({
                            filter: this.selectionFilter || null
                        })
                    }),
                    .then(response => {
                        if (!response.ok) {
                            throw new Error(`HTTP error: ${res.status} ${res.statusText}`);
                        }
                        return response.json();
                    })
                    .then(res => { resolve(res); })
                    .catch(error => {
                        console.error(error);
                        reject(error);
                    });
                });
            }
        }
    }
    </script>
    React
    App.js
    import 'whatwg-fetch';
    // ...
    class App extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                selectionFilter: ['Task_Status', '=', 'Completed']
            }
            this.handleOptionChange = this.handleOptionChange.bind(this);
        }
        handleOptionChange(e) {
            if(e.fullName === 'selectionFilter') {
                this.setState({
                    selectionFilter: e.value
                });
            }
        }
        render() {
            return (
                <DataGrid ...
                    selectionFilter={this.state.selectionFilter}
                    onOptionChanged={this.handleOptionChange}>
                </DataGrid>
            );
        }
        sendSelectedRows() {
            return new Promise((resolve, reject) => {
                fetch('https://mydomain.com/MyDataService', {
                    method: 'POST',
                    body: JSON.stringify({
                        filter: this.state.selectionFilter || null
                    })
                }),
                .then(response => {
                    if (!response.ok) {
                        throw new Error(`HTTP error: ${res.status} ${res.statusText}`);
                    }
                    return response.json();
                })
                .then(res => { resolve(res); })
                .catch(error => {
                    console.error(error);
                    reject(error);
                });
            });
        }
    }
    export default App;
  • Checking whether a row is selected

    Use the isRowSelected(data) method to determine whether a row is selected.

  • Getting the selected rows' data

    In deferred mode, the getSelectedRowsData() and getSelectedRowKeys() methods return a native Promise or a jQuery.Promise when you use jQuery. Get the data within the callback function that resolves the Promise. This is data before being processed in the DataSource.

View Demo

See Also

Lookup Optimization

As a rule, a lookup column contains IDs from a main data source field but displays human-readable values from its own data source. When the DataGrid first launches, it loads data from both data sources which causes performance to decrease.

You can send the human-readable values from the server as a part of the main data source alongside the IDs (like in this example) to optimize the lookup column. In this case, use the column's calculateDisplayValue property to specify which field provides the human-readable values. With this optimization, the lookup data source is not loaded until a user starts editing the lookup column.

jQuery
JavaScript
$(function () {
    $("#dataGridContainer").dxDataGrid({
        //...
        columns: [{
            caption: "Customer",
            // "CustomerName" provides human-readable values
            calculateDisplayValue: "CustomerName",
            dataField: "CustomerID",
            lookup: {
                valueExpr: "CustomerID",
                displayExpr: "ContactName",
                dataSource: {
                    store: {
                        // ...
                        key: "CustomerID"
                    }
                }
            }
        },
        // ...
        ]
    });
});
Angular
HTML
TypeScript
<dx-data-grid ...>
    <dxi-column
        caption="Customer"
        dataField="CustomerID"
        calculateDisplayValue="CustomerName">   <!-- "CustomerName" provides human-readable values -->
        <dxo-lookup
            [dataSource]="dataSource"
            displayExpr="ContactName"
            valueExpr="CustomerID">
        </dxo-lookup>
    </dxi-column>
</dx-data-grid>
import { DxDataGridModule } from "devextreme-angular";
import "devextreme/data/array_store";
// or
// import "devextreme/data/odata/store";
// import "devextreme/data/custom_store";
export class AppComponent {
    dataSource: any;
    constructor() {
        this.dataSource = {
            store: {
                // ...
                key: "CustomerID"
            }
        }
    }
}
@NgModule({
    imports: [
        // ...
        DxDataGridModule
    ],
    // ...
})
Vue
App.vue
<template>
    <DxDataGrid ... >
        <DxColumn
            caption="Customer"
            data-field="CustomerID"
            calculate-display-value="CustomerName">   <!-- "CustomerName" provides human-readable values -->
            <DxLookup
                :data-source="lookupDataSourceConfig"
                display-expr="ContactName"
                value-expr="CustomerID"
            />
        </DxColumn>
    </DxDataGrid>
</template>

<script>
import 'devextreme/dist/css/dx.common.css';
import 'devextreme/dist/css/dx.light.css';

import DxDataGrid, {
    DxColumn,
    DxLookup
} from 'devextreme-vue/data-grid';

import 'devextreme/data/array_store';
// ===== or =====
// import "devextreme/data/odata/store";
// import "devextreme/data/custom_store";

const lookupDataSourceConfig = {
    store: {
        // ...
        key: 'CustomerID'
    }    
}

export default {
    components: {
        DxDataGrid,
        DxColumn,
        DxLookup
    },
    data() {
        return {
            lookupDataSourceConfig
        }
    }
}
</script>
React
App.js
import React from 'react';

import 'devextreme/dist/css/dx.common.css';
import 'devextreme/dist/css/dx.light.css';

import DataGrid, {
    Column,
    Lookup
} from 'devextreme-react/data-grid';

import 'devextreme/data/array_store';
// ===== or =====
// import "devextreme/data/odata/store";
// import "devextreme/data/custom_store";

const lookupDataSourceConfig = {
    store: {
        // ...
        key: 'CustomerID'
    }    
}

class App extends React.Component {
    render() {
        return (
            <DataGrid ... >
                <Column
                    caption="Customer"
                    dataField="CustomerID"
                    calculateDisplayValue="CustomerName">   <!-- "CustomerName" provides human-readable values -->
                    <Lookup
                        dataSource={lookupDataSourceConfig}
                        displayExpr="ContactName"
                        valueExpr="CustomerID"
                    />
                </Column>
            </DataGrid>
        );
    }
}
export default App;
See Also

Data Navigation

We recommend configuring one of the following features for a user to navigate a large dataset:

  • Pager
    The pager allows a user to switch between pages in the dataset. This is the default navigation method.

  • Virtual scrolling
    Virtual scrolling allows users to scroll page by page or jump to any page in the dataset. To enable it, set scrolling.mode to "virtual".

  • Infinite scrolling
    In this mode, users scroll data page by page, but cannot skip pages. Use this mode if the virtual scrolling mode reduces performance, and navigation via the pager does not suit your case. To enable infinite scrolling, set scrolling.mode to "infinite".

Use the paging.pageSize to specify the number of rows that are loaded at a time. The bigger this value, the fewer requests are sent to the database. However, this increases the data transfer rate. This property is applied regardless of the specified navigation method.

Rendering Optimization

If you work with a large data set, follow the recommendations below to improve rendering performance:

  • Render only visible rows and columns
    The DataGrid renders all rows and columns once it loads data. To improve performance, set the rowRenderingMode and columnRenderingMode properties in the scrolling object to "virtual". In this case, UI elements are only rendered when they come into the viewport. Use virtual row rendering if paging is disabled or the pageSize is higher than the default value. Virtual column rendering should be used in grids with more than 20 columns outside the viewport.

  • Render elements with complex content last
    The grid control can render cells with simple content first, and only then proceed to cells with complex content (filter row, command columns, editors and columns with checkboxes). In this case, the control shows content sooner and the app UI can be perceived as more responsive. To load simple content first, use the renderAsync property. You can also enable the same property for individual columns if they use content-heavy cell templates.

  • Prevent columns from adjusting their widths to the content
    Do not enable columnAutoWidth property. To further optimize rendering, set a fixed width for all columns. To do this, use a column's width or a component's columnWidth properties to specify the width for each column or all columns, respectively. Note that you should use individual settings because command columns ignore columnWidth.

  • Do not use checkboxes in Boolean columns
    The checkbox is the default editor for "Boolean" dataType columns. You can increase rendering speed if you instruct the grid to render text instead. To do this, assign false to the column's showEditorAlways. Use the trueText and falseText properties to specify display strings. The default values are true and false respectively.

  • Implement onCellPrepared instead of cellTemplate for conditional formatting
    Due to the design of DevExtreme UI components, onCellPrepared works faster when you need to stylize cells based on a certain condition.