Columns can be added and removed from the Server-Side Row Model without resetting the row model.
Changing columns allows you to specify new column definitions to the grid and the grid will work out which columns are new and which are old, keeping the state of the old columns.
For the Server-Side Row Model, this means a refresh will occur in the event of one of the following:
- A row group column is added, removed or changed
- A pivot column is added, removed or changed
- While row grouping is active, a new column has an aggregation applied or changed.
- Any change is applied to a columns sort direction, or a sorted column is changed, added or removed.
Example Changing Columns Copy Link
The example below demonstrates how changing columns impacts the server side row model. The following can be noted:
- Adding or removing Athlete, Age or Sport will not reload the data as they have no row group, pivot, value, sort or filter set.
- Adding or removing Country or Year will reload the data as they are part of the grouping.
- Removing Gold, Silver or Bronze will not reload the data. Adding Gold, Silver or Bronze will reload the data as they have aggregations applied.
- If you apply a sort or filter (on Athlete) and then remove the column the data will reload.
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import "./styles.css";
import {
AutoGroupColumnDef,
ColDef,
ColGroupDef,
GridApi,
GridOptions,
GridReadyEvent,
IServerSideDatasource,
IServerSideGetRowsParams,
ModuleRegistry,
NumberFilterModule,
RowModelType,
SetFilterValuesFuncParams,
enableDevValidations,
} from "ag-grid-community";
import {
ColumnMenuModule,
ColumnsToolPanelModule,
ContextMenuModule,
RowGroupingModule,
ServerSideRowModelModule,
SetFilterModule,
} from "ag-grid-enterprise";
import { FakeServer } from "./fakeServer";
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
NumberFilterModule,
ColumnsToolPanelModule,
ColumnMenuModule,
ContextMenuModule,
RowGroupingModule,
ServerSideRowModelModule,
SetFilterModule,
]);
import { IOlympicData } from "./interfaces";
@Component({
selector: "my-app",
standalone: true,
imports: [AgGridAngular],
template: `<div class="test-container">
<div class="test-header">Select columns to show then hit 'Apply'</div>
<div class="test-header">
<label><input type="checkbox" id="athlete" />Athlete</label>
<label><input type="checkbox" id="age" />Age</label>
<label><input type="checkbox" id="country" />Country</label>
<label><input type="checkbox" id="year" />Year</label>
<label><input type="checkbox" id="sport" />Sport</label>
<label><input type="checkbox" id="gold" />Gold</label>
<label><input type="checkbox" id="silver" />Silver</label>
<label><input type="checkbox" id="bronze" />Bronze</label>
<button (click)="onBtApply()">Apply</button>
</div>
<ag-grid-angular
style="width: 100%; height: 100%;"
class="test-grid"
[columnDefs]="columnDefs"
[defaultColDef]="defaultColDef"
[autoGroupColumnDef]="autoGroupColumnDef"
[maintainColumnOrder]="true"
[rowModelType]="rowModelType"
[suppressAggFuncInHeader]="true"
[rowData]="rowData"
(gridReady)="onGridReady($event)"
/>
</div> `,
})
export class AppComponent {
private gridApi!: GridApi<IOlympicData>;
columnDefs: ColDef[] = [
colDefAthlete,
colDefAge,
colDefCountry,
colDefYear,
colDefSport,
colDefGold,
colDefSilver,
colDefBronze,
];
defaultColDef: ColDef = {
initialFlex: 1,
minWidth: 120,
};
autoGroupColumnDef: AutoGroupColumnDef = {
minWidth: 200,
};
rowModelType: RowModelType = "serverSide";
rowData!: IOlympicData[];
constructor(private http: HttpClient) {}
onBtApply() {
const cols = [];
if (getBooleanValue("#athlete")) {
cols.push(colDefAthlete);
}
if (getBooleanValue("#age")) {
cols.push(colDefAge);
}
if (getBooleanValue("#country")) {
cols.push(colDefCountry);
}
if (getBooleanValue("#year")) {
cols.push(colDefYear);
}
if (getBooleanValue("#sport")) {
cols.push(colDefSport);
}
if (getBooleanValue("#gold")) {
cols.push(colDefGold);
}
if (getBooleanValue("#silver")) {
cols.push(colDefSilver);
}
if (getBooleanValue("#bronze")) {
cols.push(colDefBronze);
}
this.gridApi.setGridOption("columnDefs", cols);
}
onGridReady(params: GridReadyEvent<IOlympicData>) {
this.gridApi = params.api;
(document.getElementById("athlete") as HTMLInputElement).checked = true;
(document.getElementById("age") as HTMLInputElement).checked = true;
(document.getElementById("country") as HTMLInputElement).checked = true;
(document.getElementById("year") as HTMLInputElement).checked = true;
(document.getElementById("sport") as HTMLInputElement).checked = true;
(document.getElementById("gold") as HTMLInputElement).checked = true;
(document.getElementById("silver") as HTMLInputElement).checked = true;
(document.getElementById("bronze") as HTMLInputElement).checked = true;
this.http
.get<
IOlympicData[]
>("https://www.ag-grid.com/example-assets/olympic-winners.json")
.subscribe((data) => {
// setup the fake server with entire dataset
fakeServer = new FakeServer(data);
// create datasource with a reference to the fake server
const datasource: IServerSideDatasource =
getServerSideDatasource(fakeServer);
// register the datasource with the grid
params.api!.setGridOption("serverSideDatasource", datasource);
});
}
}
const colDefCountry: ColDef = { field: "country", rowGroup: true };
const colDefYear: ColDef = { field: "year", rowGroup: true };
const colDefAthlete: ColDef = {
field: "athlete",
filter: "agSetColumnFilter",
filterParams: {
values: getAthletesAsync,
},
suppressHeaderMenuButton: true,
suppressHeaderContextMenu: true,
};
const colDefAge: ColDef = { field: "age" };
const colDefSport: ColDef = { field: "sport" };
const colDefGold: ColDef = { field: "gold", aggFunc: "sum" };
const colDefSilver: ColDef = { field: "silver", aggFunc: "sum" };
const colDefBronze: ColDef = { field: "bronze", aggFunc: "sum" };
function getAthletesAsync(params: SetFilterValuesFuncParams) {
const countries = fakeServer.getAthletes();
// simulating real server call with a 500ms delay
setTimeout(() => {
params.success(countries);
}, 500);
}
function getBooleanValue(cssSelector: string) {
return (
(document.querySelector(cssSelector) as HTMLInputElement).checked === true
);
}
function getServerSideDatasource(server: any): IServerSideDatasource {
return {
getRows: (params: IServerSideGetRowsParams) => {
console.log("[Datasource] - rows requested by grid: ", params.request);
const response = server.getData(params.request);
// adding delay to simulate real server call
setTimeout(() => {
if (response.success) {
// call the success callback
params.success({
rowData: response.rows,
rowCount: response.lastRow,
});
} else {
// inform the grid request failed
params.fail();
}
}, 200);
},
};
}
var fakeServer: any = undefined;
.test-grid {
height: 1px;
min-height: 1px;
flex-grow: 1;
}
.test-container {
height: 100%;
display: flex;
flex-direction: column;
}
.test-header {
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 13px;
margin-bottom: 5px;
}
.test-header input {
position: relative;
top: 2px;
}
// 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),
};
},
getAthletes: function () {
const sql = 'SELECT DISTINCT athlete FROM ? ORDER BY athlete ASC';
return alasql(sql, [allData]).map(function (x) {
return x.athlete;
});
},
};
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()],
});
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
}