Your search did not match any results.
Data Grid

Collaborative Editing


This demo shows how users can use a SignalR service that broadcasts push notifications to edit the DataGrid's data in real time. You can open this page in another browser window and start editing to see this functionality. Changes made in one window are repeated in the other.

You can share the session ID (under the DataGrid) with other users to edit the DataGrid collectively.

Note that all changes are lost if you refresh the page because the SignalR service only broadcasts changes without saving them.

Copy to CodeSandBox
<div id="data-grid-demo"> <dx-data-grid id="gridContainer" [dataSource]="dataSource" [showBorders]="true" [repaintChangesOnly]="true" (onSelectionChanged)="selectionChanged($event)"> <dxo-paging [enabled]="false"></dxo-paging> <dxo-editing refreshMode="repaint" mode="cell" [allowUpdating]="true" [allowDeleting]="true" [allowAdding]="true"> </dxo-editing> <dxi-column dataField="Prefix" caption="Title" [width]="55"></dxi-column> <dxi-column dataField="FirstName"></dxi-column> <dxi-column dataField="LastName"></dxi-column> <dxi-column dataField="Position" [width]="170"></dxi-column> <dxi-column dataField="StateID" caption="State" [width]="125"> <dxo-lookup [dataSource]="stateDataSource" displayExpr="Name" valueExpr="ID"> </dxo-lookup> </dxi-column> <dxi-column dataField="BirthDate" dataType="date"> </dxi-column> </dx-data-grid> <div class="options"> <div class="caption">Options</div> <div class="option"> <span>Session ID:</span> <dx-text-box id="sessionId" [value]="sessionId" (onValueChanged)="textBoxValueChanged($event)"> </dx-text-box> </div> </div> </div>
import { NgModule, Component, enableProdMode } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { DxDataGridModule, DxTextBoxModule } from 'devextreme-angular'; import { HubConnectionBuilder, HttpTransportType } from '@aspnet/signalr'; import * as AspNetData from 'devextreme-aspnet-data-nojquery'; const BASE_PATH = ''; if(!/localhost/.test( { enableProdMode(); } @Component({ selector: 'demo-app', templateUrl: 'app/app.component.html', styleUrls: ['app/app.component.css'], preserveWhitespaces: true }) export class AppComponent { dataSource: any; stateDataSource: any; sessionId: any; constructor() { var store = AspNetData.createStore({ key: 'ID', loadUrl: BASE_PATH + 'api/DataGridCollaborativeEditing/', insertUrl: BASE_PATH + 'api/DataGridCollaborativeEditing/', updateUrl: BASE_PATH + 'api/DataGridCollaborativeEditing/', deleteUrl: BASE_PATH + 'api/DataGridCollaborativeEditing/', onBeforeSend: function(operation, ajaxSettings) { = getSessionId(); } }); this.sessionId = getSessionId(); this.dataSource = store; this.stateDataSource = AspNetData.createStore({ key: 'ID', loadUrl: BASE_PATH + 'api/DataGridStatesLookup' }); var connection = new HubConnectionBuilder() .withUrl(BASE_PATH + 'dataGridCollaborativeEditingHub', { skipNegotiation: true, transport: HttpTransportType.WebSockets }) .build(); connection.start() .then(function () { connection.on('update', function (key, data, sessionId) { sessionId === getSessionId() && store.push([{ type: 'update', key: key, data: data }]); }); connection.on('insert', function (data, sessionId) { sessionId === getSessionId() && store.push([{ type: 'insert', data: data }]); }); connection.on('remove', function (key, sessionId) { sessionId === getSessionId() && store.push([{ type: 'remove', key: key }]); }); }); } textBoxValueChanged(e) { setSessionId(e.value); } } var SESSION_KEY = 'dx-demo-collaborative-editing-session-id'; var sessionId; function getSessionId() { if (sessionId) { return sessionId; } var value = localStorage.getItem(SESSION_KEY) || generateRandomNumber(); setSessionId(value); return value; } function setSessionId(value) { sessionId = value; localStorage.setItem(SESSION_KEY, value); } function generateRandomNumber() { var max = 1000000, min = 0; return Math.floor(Math.random() * (max - min + 1)) + min; } @NgModule({ imports: [ BrowserModule, DxDataGridModule, DxTextBoxModule ], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule { } platformBrowserDynamic().bootstrapModule(AppModule);
::ng-deep #sessionId { width: 100px; } ::ng-deep .options { padding: 20px; margin-top: 20px; background-color: rgba(191, 191, 191, 0.15); } ::ng-deep .caption { font-weight: 500; font-size: 18px; } ::ng-deep .option { margin-top: 10px; } ::ng-deep .option > span { position: relative; top: 2px; margin-right: 10px; } ::ng-deep .option > .dx-widget { width: 500px; display: inline-block; vertical-align: middle; }
// 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: // System.config({ transpiler: 'ts', typescriptOptions: { module: "commonjs", emitDecoratorMetadata: true, experimentalDecorators: true }, meta: { 'typescript': { "exports": "ts" }, 'devextreme-aspnet-data-nojquery': { "esModule": true } }, paths: { 'npm:': '' }, map: { 'ts': 'npm:plugin-typescript@8.0.0/lib/plugin.js', 'typescript': 'npm:typescript@3.4.5/lib/typescript.js', '@angular/core': 'npm:@angular/core@8.0.0/bundles/core.umd.js', '@angular/common': 'npm:@angular/common@8.0.0/bundles/common.umd.js', '@angular/compiler': 'npm:@angular/compiler@8.0.0/bundles/compiler.umd.js', '@angular/platform-browser': 'npm:@angular/platform-browser@8.0.0/bundles/platform-browser.umd.js', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic@8.0.0/bundles/platform-browser-dynamic.umd.js', '@angular/router': 'npm:@angular/router@8.0.0/bundles/router.umd.js', '@angular/forms': 'npm:@angular/forms@8.0.0/bundles/forms.umd.js', '@angular/common/http': 'npm:@angular/common@8.0.0/bundles/common-http.umd.js', '@aspnet/signalr': 'npm:@aspnet/signalr@1.0.0/dist/cjs/index.js', 'tslib': 'npm:tslib/tslib.js', 'rxjs': 'npm:rxjs@6.3.3', 'rxjs/operators': 'npm:rxjs@6.3.3/operators', 'devextreme': 'npm:devextreme@19.1', 'jszip': 'npm:jszip@3.1.3/dist/jszip.min.js', 'quill': 'npm:quill@1.3.7/dist/quill.js', 'devexpress-diagram': 'npm:devexpress-diagram', 'devextreme-angular': 'npm:devextreme-angular@19.1', 'devextreme-aspnet-data-nojquery': 'npm:devextreme-aspnet-data-nojquery@2.5.1' }, packages: { 'app': { main: './app.component.ts', defaultExtension: 'ts' }, 'devextreme': { defaultExtension: 'js' }, 'rxjs': { main: 'index.js', defaultExtension: 'js' }, 'rxjs/operators': { main: 'index.js', defaultExtension: 'js' }, 'devextreme-angular': { main: 'index.js', defaultExtension: 'js' } } });
<!DOCTYPE html> <html xmlns=""> <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=1.0" /> <link rel="stylesheet" type="text/css" href="" /> <link rel="stylesheet" type="text/css" href="" /> <script src=""></script> <script src=""></script> <script src=""></script> <script src=""></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>