DevExtreme v24.1 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

Deferred Selection

If you enable deferred row selection, the grid does not request selected rows' data with every selection change. For example, if a user clicks the checkbox in the column header to select all the rows, the grid does not immediately fetch all data from the server.

This is helpful in the following cases:

  • You process data on the server and do not want to load the selected rows' data.
  • You do process selected records on the client, but want to reduce the number of requests that are sent.
Backend API
<dx-data-grid id="grid-container" [dataSource]="dataSource" [selectionFilter]="['Task_Status', '=', 'Completed']" [showBorders]="true" > <dxo-selection mode="multiple" [deferred]="true"></dxo-selection> <dxo-filter-row [visible]="true"></dxo-filter-row> <dxi-column caption="Subject" dataField="Task_Subject"></dxi-column> <dxi-column caption="Start Date" dataField="Task_Start_Date" width="auto" dataType="date" ></dxi-column> <dxi-column caption="Due Date" dataField="Task_Due_Date" width="auto" dataType="date" ></dxi-column> <dxi-column caption="Assigned To" dataField="ResponsibleEmployee.Employee_Full_Name" width="auto" [allowSorting]="false" ></dxi-column> <dxi-column caption="Status" dataField="Task_Status" width="auto" ></dxi-column> </dx-data-grid> <div class="selection-summary center"> <dx-button id="calculateButton" text="Get statistics on the selected tasks" type="default" (onClick)="calculateStatistics()" ></dx-button> <div> <div class="column"> <span class="text count">Task count:</span> <span class="value">{{ taskCount }}</span> </div> <div class="column"> <span class="text people-count">People assigned:</span> <span class="value">{{ peopleCount }}</span> </div> <div class="column"> <span class="text avg-duration">Average task duration (days):</span> <span class="value">{{ avgDuration }}</span> </div> </div> </div>
import { NgModule, Component, ViewChild, AfterViewInit, enableProdMode, } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { DxDataGridModule, DxDataGridComponent, DxButtonModule } from 'devextreme-angular'; import query from 'devextreme/data/query'; import 'devextreme/data/odata/store'; import { Options as DataSourceOptions } from 'devextreme/data/data_source'; if (!/localhost/.test(document.location.host)) { enableProdMode(); } @Component({ selector: 'demo-app', templateUrl: 'app/app.component.html', styleUrls: ['app/app.component.css'], preserveWhitespaces: true, }) export class AppComponent implements AfterViewInit { @ViewChild(DxDataGridComponent, { static: false }) dataGrid: DxDataGridComponent; MILLISECONDS_IN_DAY = 1000 * 60 * 60 * 24; dataSource: DataSourceOptions = { store: { type: 'odata', version: 2, url: 'https://js.devexpress.com/Demos/DevAV/odata/Tasks', key: 'Task_ID', }, expand: 'ResponsibleEmployee', select: [ 'Task_ID', 'Task_Subject', 'Task_Start_Date', 'Task_Due_Date', 'Task_Status', 'ResponsibleEmployee/Employee_Full_Name', ], }; taskCount = 0; peopleCount = 0; avgDuration = 0; ngAfterViewInit() { this.calculateStatistics(); } async calculateStatistics() { const selectedItems = await this.dataGrid.instance.getSelectedRowsData(); const totalDuration = selectedItems.reduce((currentValue, item) => { const duration = item.Task_Due_Date - item.Task_Start_Date; return currentValue + duration; }, 0); const averageDurationInDays = totalDuration / this.MILLISECONDS_IN_DAY / selectedItems.length; this.taskCount = selectedItems.length; this.peopleCount = query(selectedItems) .groupBy('ResponsibleEmployee.Employee_Full_Name') .toArray().length; this.avgDuration = Math.round(averageDurationInDays) || 0; } } @NgModule({ imports: [ BrowserModule, DxDataGridModule, DxButtonModule, ], declarations: [AppComponent], bootstrap: [AppComponent], }) export class AppModule { } platformBrowserDynamic().bootstrapModule(AppModule);
::ng-deep #grid-container { height: 400px; } ::ng-deep .center { text-align: center; } ::ng-deep .selection-summary { border: 1px solid rgba(161, 161, 161, 0.2); padding: 25px; } ::ng-deep .column { margin: 20px 30px 0 0; display: inline-block; white-space: nowrap; text-align: right; } ::ng-deep .value { font-size: 40px; display: inline-block; vertical-align: middle; } ::ng-deep .text { text-align: left; white-space: normal; display: inline-block; vertical-align: middle; } ::ng-deep .avg-duration { width: 100px; } ::ng-deep .count { width: 40px; } ::ng-deep .people-count { width: 65px; }
// In real applications, you should not transpile code in the browser. // You can see how to create your own application with Angular and DevExtreme here: // https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Create_a_DevExtreme_Application/ const componentNames = [ 'accordion', 'action-sheet', 'autocomplete', 'bar-gauge', 'box', 'bullet', 'button-group', 'button', 'calendar', 'chart', 'check-box', 'circular-gauge', 'color-box', 'context-menu', 'data-grid', 'date-box', 'date-range-box', 'defer-rendering', 'diagram', 'draggable', 'drawer', 'drop-down-box', 'drop-down-button', 'file-manager', 'file-uploader', 'filter-builder', 'form', 'funnel', 'gallery', 'gantt', 'html-editor', 'linear-gauge', 'list', 'load-indicator', 'load-panel', 'lookup', 'map', 'menu', 'multi-view', 'nested', 'number-box', 'pie-chart', 'pivot-grid-field-chooser', 'pivot-grid', 'polar-chart', 'popover', 'popup', 'progress-bar', 'radio-group', 'range-selector', 'range-slider', 'recurrence-editor', 'resizable', 'responsive-box', 'sankey', 'scheduler', 'scroll-view', 'select-box', 'slider', 'sortable', 'sparkline', 'speed-dial-action', 'splitter', 'switch', 'tab-panel', 'tabs', 'tag-box', 'text-area', 'text-box', 'tile-view', 'toast', 'toolbar', 'tooltip', 'tree-list', 'tree-map', 'tree-view', 'validation-group', 'validation-summary', 'validator', 'vector-map', ]; window.exports = window.exports || {}; window.config = { transpiler: 'ts', typescriptOptions: { module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, }, meta: { 'typescript': { 'exports': 'ts', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, '@angular/platform-browser-dynamic': { 'esModule': true, }, '@angular/platform-browser': { 'esModule': true, }, '@angular/core': { 'esModule': true, }, '@angular/common': { 'esModule': true, }, '@angular/common/http': { 'esModule': true, }, '@angular/animations': { 'esModule': true, }, '@angular/forms': { 'esModule': true, }, }, paths: { 'npm:': 'https://unpkg.com/', 'bundles:': '../../../../bundles/', }, map: { 'ts': 'npm:plugin-typescript@4.2.4/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.js', /* @angular */ '@angular/compiler': 'bundles:@angular/compiler.umd.js', '@angular/platform-browser-dynamic': 'bundles:@angular/platform-browser-dynamic.umd.js', '@angular/core': 'bundles:@angular/core.umd.js', '@angular/core/primitives/signals': 'bundles:@angular/core.primitives.signals.umd.js', '@angular/common': 'bundles:@angular/common.umd.js', '@angular/common/http': 'bundles:@angular/common-http.umd.js', '@angular/platform-browser': 'bundles:@angular/platform-browser.umd.js', '@angular/platform-browser/animations': 'bundles:@angular/platform-browser.umd.js', '@angular/forms': 'bundles:@angular/forms.umd.js', /* devextreme */ 'devextreme': 'npm:devextreme@24.1.3/cjs', '@devextreme/runtime': 'npm:@devextreme/runtime@3.0.13', 'devextreme/bundles/dx.all': 'npm:devextreme@24.1.3/bundles/dx.all.js', 'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.8', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.56', /* devextreme-angular umd maps */ 'devextreme-angular': 'bundles:devextreme-angular/devextreme-angular.umd.js', 'devextreme-angular/core': 'bundles:devextreme-angular/devextreme-angular-core.umd.js', ...componentNames.reduce((acc, name) => { acc[`devextreme-angular/ui/${name}`] = `bundles:devextreme-angular/devextreme-angular-ui-${name}.umd.js`; return acc; }, {}), 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', 'tslib': 'npm:tslib@2.6.1/tslib.js', 'rxjs': 'npm:rxjs@7.5.3/dist/bundles/rxjs.umd.js', 'rxjs/operators': 'npm:rxjs@7.5.3/dist/cjs/operators/index.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@1.28.1/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign@1.1.0', 'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js', 'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js', 'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js', 'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js', 'inferno-hydrate': 'npm:inferno-hydrate@7.4.11/dist/inferno-hydrate.min.js', 'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js', 'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js', 'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js', // Prettier 'prettier/standalone': 'npm:prettier@2.8.8/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js', }, packages: { 'app': { main: './app.component.ts', defaultExtension: 'ts', }, 'devextreme': { defaultExtension: 'js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, 'rxjs': { defaultExtension: 'js', }, 'rxjs/operators': { defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', 'npm:@devextreme/runtime@3.0.13/inferno/package.json', 'npm:rxjs@7.5.3/package.json', 'npm:rxjs@7.5.3/operators/package.json', 'npm:devexpress-diagram@2.2.8/package.json', 'npm:devexpress-gantt@4.1.56/package.json', ], }; System.config(window.config); // System.import('@angular/compiler').catch(console.error.bind(console));
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/24.1.3/css/dx.light.css" /> <script src="https://unpkg.com/core-js@2.6.12/client/shim.min.js"></script> <script src="https://unpkg.com/zone.js@0.13.0/dist/zone.js"></script> <script src="https://unpkg.com/reflect-metadata@0.1.13/Reflect.js"></script> <script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script> <script src="config.js"></script> <script> System.import("app").catch(console.error.bind(console)); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <demo-app>Loading...</demo-app> </div> </body> </html>

This demo illustrates the second scenario. Deferred selection is enabled and the selected rows are only requested when you click the button below the grid.

To enable deferred selection in your application, set the selection.deferred property to true.

To specify the initially selected rows, use the selectionFilter property. The DataGrid updates this property's value at runtime and you can always access the applied filter. In this demo, the selectionFilter selects rows whose Status is Completed.

To load the selected rows' data, call the getSelectedRowsData() method. In deferred selection mode, this method returns a Promise. You can access row data in its fulfillment handler. In this demo, the getSelectedRowsData() method gets data objects that are then used to calculate statistics for the selected tasks.