The Server-Side Row Model displays loading rows while it requests data from the datasource, including when scrolling or expanding groups. Full-width loading rows are displayed by default; skeleton loading displays an indicator in each cell instead.
These loading rows are managed by the row model, not by the loading or loadingRows grid options. For application-controlled loading with the Client-Side Row Model, see Loading Rows.
Full Width Loading Row Copy Link
The example below demonstrates replacing the Provided Loading Component with a Custom Loading Component.
- Custom Loading Component is supplied via
gridOptions.loadingCellRenderer. - Custom Loading Component Parameters are supplied using
gridOptions.loadingCellRendererParams. - Example simulates a long delay to display the spinner clearly.
- Scrolling the grid will request more rows and again display the loading cell renderer.
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import {
ColDef,
ColGroupDef,
GridApi,
GridOptions,
GridReadyEvent,
IServerSideDatasource,
IServerSideGetRowsRequest,
ModuleRegistry,
NumberEditorModule,
NumberFilterModule,
RowModelType,
TextEditorModule,
TextFilterModule,
enableDevValidations,
} from "ag-grid-community";
import { ServerSideRowModelModule } from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
NumberEditorModule,
TextEditorModule,
TextFilterModule,
NumberFilterModule,
ServerSideRowModelModule,
]);
import { CustomLoadingCellRenderer } from "./custom-loading-cell-renderer.component";
import { IOlympicData } from "./interfaces";
@Component({
selector: "my-app",
standalone: true,
imports: [AgGridAngular, CustomLoadingCellRenderer],
template: `<div
style="height: 100%; padding-top: 25px; box-sizing: border-box"
>
<ag-grid-angular
style="width: 100%; height: 100%;"
[columnDefs]="columnDefs"
[defaultColDef]="defaultColDef"
[loadingCellRenderer]="loadingCellRenderer"
[loadingCellRendererParams]="loadingCellRendererParams"
[rowModelType]="rowModelType"
[cacheBlockSize]="cacheBlockSize"
[maxBlocksInCache]="maxBlocksInCache"
[rowData]="rowData"
(gridReady)="onGridReady($event)"
/>
</div> `,
})
export class AppComponent {
columnDefs: ColDef[] = [
{ field: "id" },
{ field: "athlete", width: 150 },
{ field: "age" },
{ field: "country" },
{ field: "year" },
{ field: "sport" },
{ field: "gold" },
{ field: "silver" },
{ field: "bronze" },
];
defaultColDef: ColDef = {
editable: true,
flex: 1,
minWidth: 100,
filter: true,
};
loadingCellRenderer: any = CustomLoadingCellRenderer;
loadingCellRendererParams: any = {
loadingMessage: "One moment please...",
};
rowModelType: RowModelType = "serverSide";
cacheBlockSize = 20;
maxBlocksInCache = 10;
rowData!: IOlympicData[];
constructor(private http: HttpClient) {}
onGridReady(params: GridReadyEvent<IOlympicData>) {
this.http
.get<
IOlympicData[]
>("https://www.ag-grid.com/example-assets/olympic-winners.json")
.subscribe((data) => {
// add id to data
let idSequence = 0;
data.forEach((item: any) => {
item.id = idSequence++;
});
const server: any = getFakeServer(data);
const datasource: IServerSideDatasource =
getServerSideDatasource(server);
params.api!.setGridOption("serverSideDatasource", datasource);
});
}
}
function getServerSideDatasource(server: any): IServerSideDatasource {
return {
getRows: (params) => {
// adding delay to simulate real server call
setTimeout(() => {
const response = server.getResponse(params.request);
if (response.success) {
// call the success callback
params.success({
rowData: response.rows,
rowCount: response.lastRow,
});
} else {
// inform the grid request failed
params.fail();
}
}, 4000);
},
};
}
function getFakeServer(allData: any[]): any {
return {
getResponse: (request: IServerSideGetRowsRequest) => {
console.log(
"asking for rows: " + request.startRow + " to " + request.endRow,
);
// take a slice of the total rows
const rowsThisPage = allData.slice(request.startRow, request.endRow);
// if on or after the last page, work out the last row.
const lastRow =
allData.length <= (request.endRow || 0) ? allData.length : -1;
return {
success: true,
rows: rowsThisPage,
lastRow: lastRow,
};
},
};
}
See Loading Cell Component for component interfaces, parameters and dynamic component selection.
Failed Loading Copy Link
When using a Custom Loading Component, you can add handling for loading failures in the component directly.
In the example below, note that:
- Custom Loading Component is supplied via
gridOptions.loadingCellRenderer. - Custom Loading Component Parameters are supplied using
gridOptions.loadingCellRendererParams. - The example simulates a long delay to display the spinner clearly and simulates a loading failure.
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import {
ColDef,
ColGroupDef,
GridApi,
GridOptions,
GridReadyEvent,
IServerSideDatasource,
IServerSideGetRowsRequest,
ModuleRegistry,
NumberEditorModule,
NumberFilterModule,
RowModelType,
TextEditorModule,
TextFilterModule,
enableDevValidations,
} from "ag-grid-community";
import { ServerSideRowModelModule } from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
NumberEditorModule,
TextEditorModule,
TextFilterModule,
NumberFilterModule,
ServerSideRowModelModule,
]);
import { CustomLoadingCellRenderer } from "./custom-loading-cell-renderer.component";
import { IOlympicData } from "./interfaces";
@Component({
selector: "my-app",
standalone: true,
imports: [AgGridAngular, CustomLoadingCellRenderer],
template: `<div
style="height: 100%; padding-top: 25px; box-sizing: border-box"
>
<ag-grid-angular
style="width: 100%; height: 100%;"
[columnDefs]="columnDefs"
[defaultColDef]="defaultColDef"
[loadingCellRenderer]="loadingCellRenderer"
[loadingCellRendererParams]="loadingCellRendererParams"
[rowModelType]="rowModelType"
[cacheBlockSize]="cacheBlockSize"
[serverSideInitialRowCount]="serverSideInitialRowCount"
[rowData]="rowData"
(gridReady)="onGridReady($event)"
/>
</div> `,
})
export class AppComponent {
columnDefs: ColDef[] = [
{ field: "id" },
{ field: "athlete", width: 150 },
{ field: "age" },
{ field: "country" },
{ field: "year" },
{ field: "sport" },
{ field: "gold" },
{ field: "silver" },
{ field: "bronze" },
];
defaultColDef: ColDef = {
editable: true,
flex: 1,
minWidth: 100,
filter: true,
};
loadingCellRenderer: any = CustomLoadingCellRenderer;
loadingCellRendererParams: any = {
loadingMessage: "One moment please...",
};
rowModelType: RowModelType = "serverSide";
cacheBlockSize = 10;
serverSideInitialRowCount = 10;
rowData!: IOlympicData[];
constructor(private http: HttpClient) {}
onGridReady(params: GridReadyEvent<IOlympicData>) {
this.http
.get<
IOlympicData[]
>("https://www.ag-grid.com/example-assets/olympic-winners.json")
.subscribe((data) => {
// add id to data
let idSequence = 0;
data.forEach((item: any) => {
item.id = idSequence++;
});
const server: any = getFakeServer(data);
const datasource: IServerSideDatasource =
getServerSideDatasource(server);
params.api!.setGridOption("serverSideDatasource", datasource);
});
}
}
function getServerSideDatasource(server: any): IServerSideDatasource {
return {
getRows: (params) => {
// adding delay to simulate real server call
setTimeout(() => {
// Fail loading to display failed loading cell renderer
params.fail();
}, 4000);
},
};
}
function getFakeServer(allData: any[]): any {
return {
getResponse: (request: IServerSideGetRowsRequest) => {
console.log(
"asking for rows: " + request.startRow + " to " + request.endRow,
);
// take a slice of the total rows
const rowsThisPage = allData.slice(request.startRow, request.endRow);
// if on or after the last page, work out the last row.
const lastRow =
allData.length <= (request.endRow || 0) ? allData.length : -1;
return {
success: true,
rows: rowsThisPage,
lastRow: lastRow,
};
},
};
}
For retrying failed datasource requests, see Load Retry.
Skeleton Loading Copy Link
The Server-Side Row Model can display loading indicators in cells by enabling suppressServerSideFullWidthLoadingRow.
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import {
ColDef,
ColGroupDef,
GridApi,
GridOptions,
GridReadyEvent,
IServerSideDatasource,
IServerSideGetRowsRequest,
ModuleRegistry,
RowModelType,
enableDevValidations,
} from "ag-grid-community";
import {
RowGroupingModule,
ServerSideRowModelModule,
} from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([ServerSideRowModelModule, RowGroupingModule]);
import { IOlympicData } from "./interfaces";
@Component({
selector: "my-app",
standalone: true,
imports: [AgGridAngular],
template: `<ag-grid-angular
style="width: 100%; height: 100%;"
[columnDefs]="columnDefs"
[defaultColDef]="defaultColDef"
[rowModelType]="rowModelType"
[suppressServerSideFullWidthLoadingRow]="true"
[cacheBlockSize]="cacheBlockSize"
[maxBlocksInCache]="maxBlocksInCache"
[rowBuffer]="rowBuffer"
[maxConcurrentDatasourceRequests]="maxConcurrentDatasourceRequests"
[blockLoadDebounceMillis]="blockLoadDebounceMillis"
[rowData]="rowData"
(gridReady)="onGridReady($event)"
/> `,
})
export class AppComponent {
columnDefs: ColDef[] = [
{ field: "country", flex: 4 },
{ field: "sport", flex: 4 },
{ field: "year", flex: 3 },
{ field: "gold", aggFunc: "sum", flex: 2 },
{ field: "silver", aggFunc: "sum", flex: 2 },
{ field: "bronze", aggFunc: "sum", flex: 2 },
];
defaultColDef: ColDef = {
minWidth: 75,
};
rowModelType: RowModelType = "serverSide";
cacheBlockSize = 5;
maxBlocksInCache = 0;
rowBuffer = 0;
maxConcurrentDatasourceRequests = 1;
blockLoadDebounceMillis = 200;
rowData!: IOlympicData[];
constructor(private http: HttpClient) {}
onGridReady(params: GridReadyEvent<IOlympicData>) {
this.http
.get<
IOlympicData[]
>("https://www.ag-grid.com/example-assets/olympic-winners.json")
.subscribe((data) => {
// add id to data
let idSequence = 0;
data.forEach((item: any) => {
item.id = idSequence++;
});
const server: any = getFakeServer(data);
const datasource: IServerSideDatasource =
getServerSideDatasource(server);
params.api!.setGridOption("serverSideDatasource", datasource);
});
}
}
function getServerSideDatasource(server: any): IServerSideDatasource {
return {
getRows: (params) => {
// adding delay to simulate real server call
setTimeout(() => {
const response = server.getResponse(params.request);
if (response.success) {
// call the success callback
params.success({
rowData: response.rows,
rowCount: response.lastRow,
});
} else {
// inform the grid request failed
params.fail();
}
}, 4000);
},
};
}
function getFakeServer(allData: any[]): any {
return {
getResponse: (request: IServerSideGetRowsRequest) => {
console.log(
"[Datasource] asking for rows: " +
request.startRow +
" to " +
request.endRow,
);
// take a slice of the total rows
const rowsThisPage = allData.slice(request.startRow, request.endRow);
const lastRow = allData.length;
return {
success: true,
rows: rowsThisPage,
lastRow: lastRow,
};
},
};
}
const gridOptions = {
suppressServerSideFullWidthLoadingRow: true,
}; Custom Loading Cells Copy Link
Set loadingCellRenderer on a column definition to customise its loading cells.
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import {
ColDef,
ColGroupDef,
GridApi,
GridOptions,
GridReadyEvent,
IServerSideDatasource,
IServerSideGetRowsRequest,
ModuleRegistry,
RowModelType,
enableDevValidations,
} from "ag-grid-community";
import {
RowGroupingModule,
ServerSideRowModelModule,
} from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([ServerSideRowModelModule, RowGroupingModule]);
import { CustomLoadingCellRenderer } from "./custom-loading-cell-renderer.component";
import { IOlympicData } from "./interfaces";
@Component({
selector: "my-app",
standalone: true,
imports: [AgGridAngular, CustomLoadingCellRenderer],
template: `<ag-grid-angular
style="width: 100%; height: 100%;"
[columnDefs]="columnDefs"
[defaultColDef]="defaultColDef"
[rowModelType]="rowModelType"
[cacheBlockSize]="cacheBlockSize"
[maxBlocksInCache]="maxBlocksInCache"
[rowBuffer]="rowBuffer"
[maxConcurrentDatasourceRequests]="maxConcurrentDatasourceRequests"
[blockLoadDebounceMillis]="blockLoadDebounceMillis"
[suppressServerSideFullWidthLoadingRow]="true"
[rowData]="rowData"
(gridReady)="onGridReady($event)"
/> `,
})
export class AppComponent {
columnDefs: ColDef[] = [
{
field: "country",
flex: 4,
loadingCellRenderer: CustomLoadingCellRenderer,
},
{ field: "sport", flex: 4 },
{ field: "year", flex: 3 },
{ field: "gold", aggFunc: "sum", flex: 2 },
{ field: "silver", aggFunc: "sum", flex: 2 },
{ field: "bronze", aggFunc: "sum", flex: 2 },
];
defaultColDef: ColDef = {
loadingCellRenderer: () => "",
minWidth: 75,
};
rowModelType: RowModelType = "serverSide";
cacheBlockSize = 5;
maxBlocksInCache = 0;
rowBuffer = 0;
maxConcurrentDatasourceRequests = 1;
blockLoadDebounceMillis = 200;
rowData!: IOlympicData[];
constructor(private http: HttpClient) {}
onGridReady(params: GridReadyEvent<IOlympicData>) {
this.http
.get<
IOlympicData[]
>("https://www.ag-grid.com/example-assets/olympic-winners.json")
.subscribe((data) => {
// add id to data
let idSequence = 0;
data.forEach((item: any) => {
item.id = idSequence++;
});
const server: any = getFakeServer(data);
const datasource: IServerSideDatasource =
getServerSideDatasource(server);
params.api!.setGridOption("serverSideDatasource", datasource);
});
}
}
function getServerSideDatasource(server: any): IServerSideDatasource {
return {
getRows: (params) => {
// adding delay to simulate real server call
setTimeout(() => {
const response = server.getResponse(params.request);
if (response.success) {
// call the success callback
params.success({
rowData: response.rows,
rowCount: response.lastRow,
});
} else {
// inform the grid request failed
params.fail();
}
}, 1000);
},
};
}
function getFakeServer(allData: any[]): any {
return {
getResponse: (request: IServerSideGetRowsRequest) => {
console.log(
"asking for rows: " + request.startRow + " to " + request.endRow,
);
// take a slice of the total rows
const rowsThisPage = allData.slice(request.startRow, request.endRow);
const lastRow = allData.length;
return {
success: true,
rows: rowsThisPage,
lastRow: lastRow,
};
},
};
}
const gridOptions = {
suppressServerSideFullWidthLoadingRow: true,
columnDefs: [
{ field: 'country', loadingCellRenderer: CustomLoadingCellRenderer },
// More columns, with no load renderer...
],
defaultColDef: {
loadingCellRenderer: () => '',
},
};The above example demonstrates the following:
suppressServerSideFullWidthLoadingRowis enabled, preventing the grid from defaulting to full width loading.loadingCellRendereris configured on the Country column, allowing a loading spinner to be displayed for just this column.loadingCellRendereris configured ondefaultColDefto leave loading cells empty in the other columns.
See Loading Cell Component for the shared component API.