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 {
ColDef,
GridApi,
GridOptions,
IServerSideDatasource,
IServerSideGetRowsRequest,
ModuleRegistry,
NumberEditorModule,
NumberFilterModule,
TextEditorModule,
TextFilterModule,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import { ServerSideRowModelModule } from "ag-grid-enterprise";
import { CustomLoadingCellRenderer } from "./customLoadingCellRenderer";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
NumberEditorModule,
TextEditorModule,
TextFilterModule,
NumberFilterModule,
ServerSideRowModelModule,
]);
const columnDefs: ColDef[] = [
{ field: "id" },
{ field: "athlete", width: 150 },
{ field: "age" },
{ field: "country" },
{ field: "year" },
{ field: "sport" },
{ field: "gold" },
{ field: "silver" },
{ field: "bronze" },
];
let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
defaultColDef: {
editable: true,
flex: 1,
minWidth: 100,
filter: true,
},
loadingCellRenderer: CustomLoadingCellRenderer,
loadingCellRendererParams: {
loadingMessage: "One moment please...",
},
columnDefs: columnDefs,
// use the server-side row model
rowModelType: "serverSide",
// fetch 20 rows per at a time
cacheBlockSize: 20,
// only keep 10 blocks of rows
maxBlocksInCache: 10,
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((response) => response.json())
.then((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);
gridApi!.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,
};
},
};
}
import type { ILoadingCellRendererComp, ILoadingCellRendererParams } from 'ag-grid-community';
export class CustomLoadingCellRenderer implements ILoadingCellRendererComp {
eGui!: HTMLElement;
init(params: ILoadingCellRendererParams & { loadingMessage: string }) {
this.eGui = document.createElement('div');
this.eGui.innerHTML = `
<div class="ag-custom-loading-cell" style="padding-left: 10px; line-height: 25px;">
<i class="fas fa-spinner fa-pulse"></i>
<span>${params.loadingMessage} </span>
</div>
`;
}
getGui() {
return this.eGui;
}
}
<div style="height: 100%; padding-top: 25px; box-sizing: border-box">
<div id="myGrid" style="height: 100%"></div>
</div>
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
} 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 {
ColDef,
GridApi,
GridOptions,
IServerSideDatasource,
IServerSideGetRowsRequest,
ModuleRegistry,
NumberEditorModule,
NumberFilterModule,
TextEditorModule,
TextFilterModule,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import { ServerSideRowModelModule } from "ag-grid-enterprise";
import { CustomLoadingCellRenderer } from "./customLoadingCellRenderer";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
NumberEditorModule,
TextEditorModule,
TextFilterModule,
NumberFilterModule,
ServerSideRowModelModule,
]);
const columnDefs: ColDef[] = [
{ field: "id" },
{ field: "athlete", width: 150 },
{ field: "age" },
{ field: "country" },
{ field: "year" },
{ field: "sport" },
{ field: "gold" },
{ field: "silver" },
{ field: "bronze" },
];
let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
defaultColDef: {
editable: true,
flex: 1,
minWidth: 100,
filter: true,
},
loadingCellRenderer: CustomLoadingCellRenderer,
loadingCellRendererParams: {
loadingMessage: "One moment please...",
},
columnDefs: columnDefs,
// use the server-side row model
rowModelType: "serverSide",
// fetch 20 rows per at a time
cacheBlockSize: 10,
serverSideInitialRowCount: 10,
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((response) => response.json())
.then((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);
gridApi!.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,
};
},
};
}
import type { ILoadingCellRendererComp, ILoadingCellRendererParams } from 'ag-grid-community';
export class CustomLoadingCellRenderer implements ILoadingCellRendererComp {
eGui!: HTMLElement;
init(params: ILoadingCellRendererParams & { loadingMessage: string }) {
this.eGui = document.createElement('div');
if (params.node.failedLoad) {
this.eGui.innerHTML = `
<div class="ag-custom-loading-cell" style="padding-left: 10px; line-height: 25px;">
<i class="fas fa-times"></i>
<span>Data failed to load</span>
</div>
`;
return;
}
this.eGui.innerHTML = `
<div class="ag-custom-loading-cell" style="padding-left: 10px; line-height: 25px;">
<i class="fas fa-spinner fa-pulse"></i>
<span>${params.loadingMessage} </span>
</div>
`;
}
getGui() {
return this.eGui;
}
}
<div style="height: 100%; padding-top: 25px; box-sizing: border-box">
<div id="myGrid" style="height: 100%"></div>
</div>
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
} 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 {
GridApi,
GridOptions,
IServerSideDatasource,
IServerSideGetRowsRequest,
ModuleRegistry,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import {
RowGroupingModule,
ServerSideRowModelModule,
} from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([ServerSideRowModelModule, RowGroupingModule]);
let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
columnDefs: [
{ 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: {
minWidth: 75,
},
// use the server-side row model
rowModelType: "serverSide",
// suppress the default full width loading behaviour
suppressServerSideFullWidthLoadingRow: true,
cacheBlockSize: 5,
maxBlocksInCache: 0,
rowBuffer: 0,
maxConcurrentDatasourceRequests: 1,
blockLoadDebounceMillis: 200,
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((response) => response.json())
.then((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);
gridApi!.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,
};
},
};
}
<div id="myGrid" style="height: 100%"></div>
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
} const gridOptions = {
suppressServerSideFullWidthLoadingRow: true,
}; Custom Loading Cells Copy Link
Set loadingCellRenderer on a column definition to customise its loading cells.
import {
GridApi,
GridOptions,
IServerSideDatasource,
IServerSideGetRowsRequest,
ModuleRegistry,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import {
RowGroupingModule,
ServerSideRowModelModule,
} from "ag-grid-enterprise";
import { CustomLoadingCellRenderer } from "./customLoadingCellRenderer";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([ServerSideRowModelModule, RowGroupingModule]);
let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
columnDefs: [
{
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: {
loadingCellRenderer: () => "",
minWidth: 75,
},
// use the server-side row model
rowModelType: "serverSide",
cacheBlockSize: 5,
maxBlocksInCache: 0,
rowBuffer: 0,
maxConcurrentDatasourceRequests: 1,
blockLoadDebounceMillis: 200,
suppressServerSideFullWidthLoadingRow: true,
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((response) => response.json())
.then((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);
gridApi!.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,
};
},
};
}
export class CustomLoadingCellRenderer {
eGui!: HTMLImageElement;
init() {
this.eGui = document.createElement('img');
this.eGui.src = 'https://www.ag-grid.com/example-assets/loading.gif';
}
getGui() {
return this.eGui;
}
}
<div id="myGrid" style="height: 100%"></div>
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
} 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.