This section demonstrates updating rows directly while using the Server-Side Row Model (SSRM).
Updating Rows API Copy Link
You can update a single row by using the row node updateData or setData functions.
Updates the data on the rowNode. When this method is called, the grid refreshes the entire rendered row if it is displayed. |
Replaces the data on the rowNode. When this method is called, the grid refreshes the entire rendered row if it is displayed. |
Setting row data will NOT change the row node ID, so if you are using getRowId() and the data changes such that the ID will be different, the rowNode will not have its ID updated.
Updating Rows Example Copy Link
The example below demonstrates a basic example, using the API's forEachNode function to iterate over all loaded nodes, and updating their version.
Set Data: Sets the row data using
setDataand the grid refreshes the row, notably the cells won't flash withenableCellChangeFlash.Update Data: Updates the row data using
updateDataand the grid refreshes the row, notably the cells do flash withenableCellChangeFlash.
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import "./styles.css";
import {
ColDef,
ColGroupDef,
GridApi,
GridOptions,
GridReadyEvent,
HighlightChangesModule,
IServerSideDatasource,
ModuleRegistry,
RowApiModule,
RowModelType,
enableDevValidations,
} from "ag-grid-community";
import {
RowGroupingModule,
ServerSideRowModelModule,
} from "ag-grid-enterprise";
import { FakeServer } from "./fakeServer";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
RowApiModule,
HighlightChangesModule,
RowGroupingModule,
ServerSideRowModelModule,
]);
@Component({
selector: "my-app",
standalone: true,
imports: [AgGridAngular],
template: `<div class="example-wrapper">
<div style="margin-bottom: 5px">
<button (click)="setRows()">Set Rows</button>
<button (click)="updateRows()">Update Rows</button>
</div>
<ag-grid-angular
style="width: 100%; height: 100%;"
[columnDefs]="columnDefs"
[defaultColDef]="defaultColDef"
[rowModelType]="rowModelType"
[cacheBlockSize]="cacheBlockSize"
[rowData]="rowData"
(gridReady)="onGridReady($event)"
/>
</div> `,
})
export class AppComponent {
private gridApi!: GridApi;
columnDefs: ColDef[] = [
{ field: "athlete" },
{ field: "date" },
{ field: "country" },
{ field: "version" },
];
defaultColDef: ColDef = {
flex: 1,
sortable: false,
enableCellChangeFlash: true,
};
rowModelType: RowModelType = "serverSide";
cacheBlockSize = 75;
rowData!: any[];
constructor(private http: HttpClient) {}
setRows() {
versionCounter += 1;
const version =
versionCounter + " - " + versionCounter + " - " + versionCounter;
this.gridApi.forEachNode((node) => {
node.setData({ ...node.data, version });
});
}
updateRows() {
versionCounter += 1;
const version =
versionCounter + " - " + versionCounter + " - " + versionCounter;
this.gridApi.forEachNode((node) => {
node.updateData({ ...node.data, version });
});
}
onGridReady(params: GridReadyEvent) {
this.gridApi = params.api;
this.http
.get<any[]>("https://www.ag-grid.com/example-assets/olympic-winners.json")
.subscribe((data) => {
// setup the fake server with entire dataset
const fakeServer = new FakeServer(data);
// create datasource with a reference to the fake server
const datasource = getServerSideDatasource(fakeServer);
// register the datasource with the grid
params.api!.setGridOption("serverSideDatasource", datasource);
});
}
}
let versionCounter: number = 0;
const getServerSideDatasource = (server: any): IServerSideDatasource => {
return {
getRows: (params) => {
console.log("[Datasource] - rows requested by grid: ", params.request);
const response = server.getData(params.request);
const dataWithVersion = response.rows.map((rowData: any) => {
return {
...rowData,
version:
versionCounter + " - " + versionCounter + " - " + versionCounter,
};
});
// adding delay to simulate real server call
setTimeout(() => {
if (response.success) {
// call the success callback
params.success({
rowData: dataWithVersion,
rowCount: response.lastRow,
});
} else {
// inform the grid request failed
params.fail();
}
}, 1000);
},
};
};
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
#myGrid {
flex: 1 1 0px;
width: 100%;
}
// This fake server uses http://alasql.org/ to mimic how a real server
// might generate sql queries from the Server-Side Row Model request.
// To keep things simple it does the bare minimum to support the example.
export function FakeServer(allData) {
alasql.options.cache = false;
return {
getData: function (request) {
const results = executeQuery(request);
return {
success: true,
rows: results,
lastRow: getLastRowIndex(request),
};
},
};
function executeQuery(request) {
const sql = buildSql(request);
console.log('[FakeServer] - about to execute query:', sql);
return alasql(sql, [allData]);
}
function buildSql(request) {
return (
selectSql(request) +
' FROM ?' +
whereSql(request) +
groupBySql(request) +
orderBySql(request) +
limitSql(request)
);
}
function selectSql(request) {
const rowGroupCols = request.rowGroupCols;
const valueCols = request.valueCols;
const groupKeys = request.groupKeys;
if (isDoingGrouping(rowGroupCols, groupKeys)) {
const rowGroupCol = rowGroupCols[groupKeys.length];
const colsToSelect = [rowGroupCol.id];
valueCols.forEach(function (valueCol) {
colsToSelect.push(valueCol.aggFunc + '(' + valueCol.id + ') AS ' + valueCol.id);
});
return 'SELECT ' + colsToSelect.join(', ');
}
return 'SELECT *';
}
function whereSql(request) {
const rowGroups = request.rowGroupCols;
const groupKeys = request.groupKeys;
const whereParts = [];
if (groupKeys) {
groupKeys.forEach(function (key, i) {
const value = typeof key === 'string' ? "'" + key + "'" : key;
whereParts.push(rowGroups[i].id + ' = ' + value);
});
}
if (whereParts.length > 0) {
return ' WHERE ' + whereParts.join(' AND ');
}
return '';
}
function groupBySql(request) {
const rowGroupCols = request.rowGroupCols;
const groupKeys = request.groupKeys;
if (isDoingGrouping(rowGroupCols, groupKeys)) {
const rowGroupCol = rowGroupCols[groupKeys.length];
return ' GROUP BY ' + rowGroupCol.id + ' HAVING count(*) > 0';
}
return '';
}
function orderBySql(request) {
const sortModel = request.sortModel;
if (sortModel.length === 0) return '';
const sorts = sortModel.map(function (s) {
return s.colId + ' ' + s.sort.toUpperCase();
});
return ' ORDER BY ' + sorts.join(', ');
}
function limitSql(request) {
if (request.endRow == undefined || request.startRow == undefined) {
return '';
}
const blockSize = request.endRow - request.startRow;
return ' LIMIT ' + blockSize + ' OFFSET ' + request.startRow;
}
function isDoingGrouping(rowGroupCols, groupKeys) {
// we are not doing grouping if at the lowest level
return rowGroupCols.length > groupKeys.length;
}
function getLastRowIndex(request) {
return executeQuery({ ...request, startRow: undefined, endRow: undefined }).length;
}
}
import '@angular/compiler';
import { provideHttpClient } from '@angular/common/http';
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
if (new URLSearchParams(window.location.search).get('prod') !== 'false') {
enableProdMode();
}
const app = bootstrapApplication(AppComponent, {
providers: [provideHttpClient()],
});
Specific Row Updates Copy Link
The following code snippet outlines the general approach of iterating through all loaded row nodes and then updating target rows with rowNode.updateData(data):
this.gridApi.forEachNode(rowNode => {
if (idsToUpdate.indexOf(rowNode.data.id) >= 0) {
// arbitrarily update some data
const updated = rowNode.data;
updated.gold += 1;
// directly update data in rowNode
rowNode.updateData(updated);
}
});The example below demonstrates this snippet in action;
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import "./styles.css";
import {
ColDef,
ColGroupDef,
GetRowIdFunc,
GridApi,
GridOptions,
GridReadyEvent,
HighlightChangesModule,
IServerSideDatasource,
ModuleRegistry,
RowApiModule,
RowModelType,
enableDevValidations,
} from "ag-grid-community";
import {
RowGroupingModule,
ServerSideRowModelModule,
} from "ag-grid-enterprise";
import { FakeServer } from "./fakeServer";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
RowApiModule,
HighlightChangesModule,
RowGroupingModule,
ServerSideRowModelModule,
]);
@Component({
selector: "my-app",
standalone: true,
imports: [AgGridAngular],
template: `<div class="example-wrapper">
<div style="margin-bottom: 5px">
<button (click)="updateRows('Michael Phelps')">
Update All Michael Phelps Records
</button>
<button (click)="updateRows('Michael Phelps', '29/08/2004')">
Update Michael Phelps, 29/08/2004
</button>
<button (click)="updateRows('Aleksey Nemov', '01/10/2000')">
Update Aleksey Nemov, 01/10/2000
</button>
<button (click)="updateRows(undefined, '12/08/2012')">
Update All Records Dated 12/08/2012
</button>
</div>
<ag-grid-angular
style="width: 100%; height: 100%;"
[columnDefs]="columnDefs"
[defaultColDef]="defaultColDef"
[rowModelType]="rowModelType"
[cacheBlockSize]="cacheBlockSize"
[getRowId]="getRowId"
[rowData]="rowData"
(gridReady)="onGridReady($event)"
/>
</div> `,
})
export class AppComponent {
private gridApi!: GridApi;
columnDefs: ColDef[] = [
{ field: "athlete" },
{ field: "date" },
{ field: "country" },
{ field: "version" },
];
defaultColDef: ColDef = {
flex: 1,
sortable: false,
enableCellChangeFlash: true,
};
rowModelType: RowModelType = "serverSide";
cacheBlockSize = 75;
getRowId: GetRowIdFunc = (params) =>
`${params.data.athlete}-${params.data.date}`;
rowData!: any[];
constructor(private http: HttpClient) {}
updateRows(athlete?: string, date?: string) {
versionCounter += 1;
this.gridApi.forEachNode((rowNode) => {
if (athlete != null && rowNode.data?.athlete !== athlete) {
// if the athlete doesn't match, skip this row
// Or row data is empty as it could be the loading row
return;
}
if (date != null && rowNode.data?.date !== date) {
return;
}
// arbitrarily update some data
const updated = rowNode.data;
updated.version =
versionCounter + " - " + versionCounter + " - " + versionCounter;
// directly update data in rowNode
rowNode.updateData(updated);
});
}
onGridReady(params: GridReadyEvent) {
this.gridApi = params.api;
this.http
.get<any[]>("https://www.ag-grid.com/example-assets/olympic-winners.json")
.subscribe((data) => {
// setup the fake server with entire dataset
const fakeServer = new FakeServer(data);
// create datasource with a reference to the fake server
const datasource = getServerSideDatasource(fakeServer);
// register the datasource with the grid
params.api!.setGridOption("serverSideDatasource", datasource);
});
}
}
let versionCounter: number = 0;
const getServerSideDatasource = (server: any): IServerSideDatasource => {
return {
getRows: (params) => {
console.log("[Datasource] - rows requested by grid: ", params.request);
const response = server.getData(params.request);
const dataWithVersion = response.rows.map((rowData: any) => {
return {
...rowData,
version:
versionCounter + " - " + versionCounter + " - " + versionCounter,
};
});
// adding delay to simulate real server call
setTimeout(() => {
if (response.success) {
// call the success callback
params.success({
rowData: dataWithVersion,
rowCount: response.lastRow,
});
} else {
// inform the grid request failed
params.fail();
}
}, 1000);
},
};
};
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
#myGrid {
flex: 1 1 0px;
width: 100%;
}
// This fake server uses http://alasql.org/ to mimic how a real server
// might generate sql queries from the Server-Side Row Model request.
// To keep things simple it does the bare minimum to support the example.
export function FakeServer(allData) {
alasql.options.cache = false;
return {
getData: function (request) {
const results = executeQuery(request);
return {
success: true,
rows: results,
lastRow: getLastRowIndex(request),
};
},
};
function executeQuery(request) {
const sql = buildSql(request);
console.log('[FakeServer] - about to execute query:', sql);
return alasql(sql, [allData]);
}
function buildSql(request) {
return (
selectSql(request) +
' FROM ?' +
whereSql(request) +
groupBySql(request) +
orderBySql(request) +
limitSql(request)
);
}
function selectSql(request) {
const rowGroupCols = request.rowGroupCols;
const valueCols = request.valueCols;
const groupKeys = request.groupKeys;
if (isDoingGrouping(rowGroupCols, groupKeys)) {
const rowGroupCol = rowGroupCols[groupKeys.length];
const colsToSelect = [rowGroupCol.id];
valueCols.forEach(function (valueCol) {
colsToSelect.push(valueCol.aggFunc + '(' + valueCol.id + ') AS ' + valueCol.id);
});
return 'SELECT ' + colsToSelect.join(', ');
}
return 'SELECT *';
}
function whereSql(request) {
const rowGroups = request.rowGroupCols;
const groupKeys = request.groupKeys;
const whereParts = [];
if (groupKeys) {
groupKeys.forEach(function (key, i) {
const value = typeof key === 'string' ? "'" + key + "'" : key;
whereParts.push(rowGroups[i].id + ' = ' + value);
});
}
if (whereParts.length > 0) {
return ' WHERE ' + whereParts.join(' AND ');
}
return '';
}
function groupBySql(request) {
const rowGroupCols = request.rowGroupCols;
const groupKeys = request.groupKeys;
if (isDoingGrouping(rowGroupCols, groupKeys)) {
const rowGroupCol = rowGroupCols[groupKeys.length];
return ' GROUP BY ' + rowGroupCol.id + ' HAVING count(*) > 0';
}
return '';
}
function orderBySql(request) {
const sortModel = request.sortModel;
if (sortModel.length === 0) return '';
const sorts = sortModel.map(function (s) {
return s.colId + ' ' + s.sort.toUpperCase();
});
return ' ORDER BY ' + sorts.join(', ');
}
function limitSql(request) {
if (request.endRow == undefined || request.startRow == undefined) {
return '';
}
const blockSize = request.endRow - request.startRow;
return ' LIMIT ' + blockSize + ' OFFSET ' + request.startRow;
}
function isDoingGrouping(rowGroupCols, groupKeys) {
// we are not doing grouping if at the lowest level
return rowGroupCols.length > groupKeys.length;
}
function getLastRowIndex(request) {
return executeQuery({ ...request, startRow: undefined, endRow: undefined }).length;
}
}
import '@angular/compiler';
import { provideHttpClient } from '@angular/common/http';
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
if (new URLSearchParams(window.location.search).get('prod') !== 'false') {
enableProdMode();
}
const app = bootstrapApplication(AppComponent, {
providers: [provideHttpClient()],
});
Selected Row Updates Copy Link
The example below demonstrates how to update all of the rows which the user has selected, note the following:
- The Update Selected Rows button will update the row version directly on the selected row nodes.
- The selected nodes are obtained using
api.getSelectedNodes(), and are then individually updated.
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import "./styles.css";
import {
ColDef,
ColGroupDef,
GetRowIdFunc,
GridApi,
GridOptions,
GridReadyEvent,
HighlightChangesModule,
IServerSideDatasource,
ModuleRegistry,
RowModelType,
RowSelectionOptions,
enableDevValidations,
} from "ag-grid-community";
import {
RowGroupingModule,
ServerSideRowModelModule,
} from "ag-grid-enterprise";
import { FakeServer } from "./fakeServer";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
HighlightChangesModule,
RowGroupingModule,
ServerSideRowModelModule,
]);
@Component({
selector: "my-app",
standalone: true,
imports: [AgGridAngular],
template: `<div class="example-wrapper">
<div style="margin-bottom: 5px">
<button (click)="updateSelectedRows()">Update Selected Rows</button>
</div>
<ag-grid-angular
style="width: 100%; height: 100%;"
[columnDefs]="columnDefs"
[defaultColDef]="defaultColDef"
[rowSelection]="rowSelection"
[rowModelType]="rowModelType"
[cacheBlockSize]="cacheBlockSize"
[getRowId]="getRowId"
[rowData]="rowData"
(gridReady)="onGridReady($event)"
/>
</div> `,
})
export class AppComponent {
private gridApi!: GridApi;
columnDefs: ColDef[] = [
{ field: "athlete" },
{ field: "country" },
{ field: "date" },
{ field: "version" },
];
defaultColDef: ColDef = {
flex: 1,
sortable: false,
enableCellChangeFlash: true,
};
rowSelection: RowSelectionOptions | "single" | "multiple" = {
mode: "multiRow",
headerCheckbox: false,
};
rowModelType: RowModelType = "serverSide";
cacheBlockSize = 75;
getRowId: GetRowIdFunc = (params) =>
`${params.data.athlete}-${params.data.date}`;
rowData!: any[];
constructor(private http: HttpClient) {}
updateSelectedRows() {
versionCounter += 1;
const version =
versionCounter + " - " + versionCounter + " - " + versionCounter;
const nodesToUpdate = this.gridApi.getSelectedNodes();
nodesToUpdate.forEach((node) => {
node.updateData({ ...node.data, version });
});
}
onGridReady(params: GridReadyEvent) {
this.gridApi = params.api;
this.http
.get<any[]>("https://www.ag-grid.com/example-assets/olympic-winners.json")
.subscribe((data) => {
// setup the fake server with entire dataset
const fakeServer = new FakeServer(data);
// create datasource with a reference to the fake server
const datasource = getServerSideDatasource(fakeServer);
// register the datasource with the grid
params.api!.setGridOption("serverSideDatasource", datasource);
});
}
}
let versionCounter: number = 0;
const getServerSideDatasource = (server: any): IServerSideDatasource => {
return {
getRows: (params) => {
console.log("[Datasource] - rows requested by grid: ", params.request);
const response = server.getData(params.request);
const dataWithVersion = response.rows.map((rowData: any) => {
return {
...rowData,
version:
versionCounter + " - " + versionCounter + " - " + versionCounter,
};
});
// adding delay to simulate real server call
setTimeout(() => {
if (response.success) {
// call the success callback
params.success({
rowData: dataWithVersion,
rowCount: response.lastRow,
});
} else {
// inform the grid request failed
params.fail();
}
}, 1000);
},
};
};
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
#myGrid {
flex: 1 1 0px;
width: 100%;
}
// This fake server uses http://alasql.org/ to mimic how a real server
// might generate sql queries from the Server-Side Row Model request.
// To keep things simple it does the bare minimum to support the example.
export function FakeServer(allData) {
alasql.options.cache = false;
return {
getData: function (request) {
const results = executeQuery(request);
return {
success: true,
rows: results,
lastRow: getLastRowIndex(request),
};
},
};
function executeQuery(request) {
const sql = buildSql(request);
console.log('[FakeServer] - about to execute query:', sql);
return alasql(sql, [allData]);
}
function buildSql(request) {
return (
selectSql(request) +
' FROM ?' +
whereSql(request) +
groupBySql(request) +
orderBySql(request) +
limitSql(request)
);
}
function selectSql(request) {
const rowGroupCols = request.rowGroupCols;
const valueCols = request.valueCols;
const groupKeys = request.groupKeys;
if (isDoingGrouping(rowGroupCols, groupKeys)) {
const rowGroupCol = rowGroupCols[groupKeys.length];
const colsToSelect = [rowGroupCol.id];
valueCols.forEach(function (valueCol) {
colsToSelect.push(valueCol.aggFunc + '(' + valueCol.id + ') AS ' + valueCol.id);
});
return 'SELECT ' + colsToSelect.join(', ');
}
return 'SELECT *';
}
function whereSql(request) {
const rowGroups = request.rowGroupCols;
const groupKeys = request.groupKeys;
const whereParts = [];
if (groupKeys) {
groupKeys.forEach(function (key, i) {
const value = typeof key === 'string' ? "'" + key + "'" : key;
whereParts.push(rowGroups[i].id + ' = ' + value);
});
}
if (whereParts.length > 0) {
return ' WHERE ' + whereParts.join(' AND ');
}
return '';
}
function groupBySql(request) {
const rowGroupCols = request.rowGroupCols;
const groupKeys = request.groupKeys;
if (isDoingGrouping(rowGroupCols, groupKeys)) {
const rowGroupCol = rowGroupCols[groupKeys.length];
return ' GROUP BY ' + rowGroupCol.id + ' HAVING count(*) > 0';
}
return '';
}
function orderBySql(request) {
const sortModel = request.sortModel;
if (sortModel.length === 0) return '';
const sorts = sortModel.map(function (s) {
return s.colId + ' ' + s.sort.toUpperCase();
});
return ' ORDER BY ' + sorts.join(', ');
}
function limitSql(request) {
if (request.endRow == undefined || request.startRow == undefined) {
return '';
}
const blockSize = request.endRow - request.startRow;
return ' LIMIT ' + blockSize + ' OFFSET ' + request.startRow;
}
function isDoingGrouping(rowGroupCols, groupKeys) {
// we are not doing grouping if at the lowest level
return rowGroupCols.length > groupKeys.length;
}
function getLastRowIndex(request) {
return executeQuery({ ...request, startRow: undefined, endRow: undefined }).length;
}
}
import '@angular/compiler';
import { provideHttpClient } from '@angular/common/http';
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
if (new URLSearchParams(window.location.search).get('prod') !== 'false') {
enableProdMode();
}
const app = bootstrapApplication(AppComponent, {
providers: [provideHttpClient()],
});