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.
("use client");
import React, {
useCallback,
useMemo,
useRef,
useState,
StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
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";
import CustomLoadingCellRenderer from "./customLoadingCellRenderer.tsx";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
const modules = [
NumberEditorModule,
TextEditorModule,
TextFilterModule,
NumberFilterModule,
ServerSideRowModelModule,
];
const getServerSideDatasource: (server: any) => IServerSideDatasource = (
server: any,
) => {
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);
},
};
};
const getFakeServer: (allData: any[]) => any = (allData: 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,
};
},
};
};
const GridExample = () => {
const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
const [columnDefs, setColumnDefs] = useState<ColDef[]>([
{ field: "id" },
{ field: "athlete", width: 150 },
{ field: "age" },
{ field: "country" },
{ field: "year" },
{ field: "sport" },
{ field: "gold" },
{ field: "silver" },
{ field: "bronze" },
]);
const defaultColDef = useMemo<ColDef>(() => {
return {
editable: true,
flex: 1,
minWidth: 100,
filter: true,
};
}, []);
const loadingCellRenderer = useCallback(CustomLoadingCellRenderer, []);
const loadingCellRendererParams = useMemo(() => {
return {
loadingMessage: "One moment please...",
};
}, []);
const onGridReady = useCallback((params: GridReadyEvent) => {
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((resp) => resp.json())
.then((data: IOlympicData[]) => {
// 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);
});
}, []);
return (
<AgGridProvider modules={modules}>
<div style={containerStyle}>
<div
style={{
height: "100%",
paddingTop: "25px",
boxSizing: "border-box",
}}
>
<div style={gridStyle}>
<AgGridReact<IOlympicData>
columnDefs={columnDefs}
defaultColDef={defaultColDef}
loadingCellRenderer={loadingCellRenderer}
loadingCellRendererParams={loadingCellRendererParams}
rowModelType={"serverSide"}
cacheBlockSize={20}
maxBlocksInCache={10}
onGridReady={onGridReady}
/>
</div>
</div>
</div>
</AgGridProvider>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(
<StrictMode>
<GridExample />
</StrictMode>,
);
import React from 'react';
import type { CustomLoadingCellRendererProps } from 'ag-grid-react';
export default (props: CustomLoadingCellRendererProps & { loadingMessage: string }) => {
return (
<div className="ag-custom-loading-cell" style={{ paddingLeft: '10px', lineHeight: '25px' }}>
<i className="fas fa-spinner fa-pulse"></i> <span> {props.loadingMessage}</span>
</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.
("use client");
import React, {
useCallback,
useMemo,
useRef,
useState,
StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
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";
import CustomLoadingCellRenderer from "./customLoadingCellRenderer.tsx";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
const modules = [
NumberEditorModule,
TextEditorModule,
TextFilterModule,
NumberFilterModule,
ServerSideRowModelModule,
];
const getServerSideDatasource: (server: any) => IServerSideDatasource = (
server: any,
) => {
return {
getRows: (params) => {
// adding delay to simulate real server call
setTimeout(() => {
// Fail loading to display failed loading cell renderer
params.fail();
}, 4000);
},
};
};
const getFakeServer: (allData: any[]) => any = (allData: 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,
};
},
};
};
const GridExample = () => {
const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
const [columnDefs, setColumnDefs] = useState<ColDef[]>([
{ field: "id" },
{ field: "athlete", width: 150 },
{ field: "age" },
{ field: "country" },
{ field: "year" },
{ field: "sport" },
{ field: "gold" },
{ field: "silver" },
{ field: "bronze" },
]);
const defaultColDef = useMemo<ColDef>(() => {
return {
editable: true,
flex: 1,
minWidth: 100,
filter: true,
};
}, []);
const loadingCellRenderer = useCallback(CustomLoadingCellRenderer, []);
const loadingCellRendererParams = useMemo(() => {
return {
loadingMessage: "One moment please...",
};
}, []);
const onGridReady = useCallback((params: GridReadyEvent) => {
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((resp) => resp.json())
.then((data: IOlympicData[]) => {
// 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);
});
}, []);
return (
<AgGridProvider modules={modules}>
<div style={containerStyle}>
<div
style={{
height: "100%",
paddingTop: "25px",
boxSizing: "border-box",
}}
>
<div style={gridStyle}>
<AgGridReact<IOlympicData>
columnDefs={columnDefs}
defaultColDef={defaultColDef}
loadingCellRenderer={loadingCellRenderer}
loadingCellRendererParams={loadingCellRendererParams}
rowModelType={"serverSide"}
cacheBlockSize={10}
serverSideInitialRowCount={10}
onGridReady={onGridReady}
/>
</div>
</div>
</div>
</AgGridProvider>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(
<StrictMode>
<GridExample />
</StrictMode>,
);
import React from 'react';
import type { CustomLoadingCellRendererProps } from 'ag-grid-react';
export default (props: CustomLoadingCellRendererProps & { loadingMessage: string }) => {
if (props.node.failedLoad) {
return (
<div className="ag-custom-loading-cell" style={{ paddingLeft: '10px', lineHeight: '25px' }}>
<i className="fas fa-times"></i>
<span> Data failed to load</span>
</div>
);
}
return (
<div className="ag-custom-loading-cell" style={{ paddingLeft: '10px', lineHeight: '25px' }}>
<i className="fas fa-spinner fa-pulse"></i> <span> {props.loadingMessage}</span>
</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.
("use client");
import React, {
useCallback,
useMemo,
useRef,
useState,
StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
ColDef,
ColGroupDef,
GridApi,
GridOptions,
GridReadyEvent,
IServerSideDatasource,
IServerSideGetRowsRequest,
ModuleRegistry,
RowModelType,
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();
}
const modules = [ServerSideRowModelModule, RowGroupingModule];
const getServerSideDatasource: (server: any) => IServerSideDatasource = (
server: any,
) => {
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);
},
};
};
const getFakeServer: (allData: any[]) => any = (allData: 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 GridExample = () => {
const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
const [columnDefs, setColumnDefs] = useState<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 },
]);
const defaultColDef = useMemo<ColDef>(() => {
return {
minWidth: 75,
};
}, []);
const onGridReady = useCallback((params: GridReadyEvent) => {
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((resp) => resp.json())
.then((data: IOlympicData[]) => {
// 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);
});
}, []);
return (
<AgGridProvider modules={modules}>
<div style={containerStyle}>
<div style={gridStyle}>
<AgGridReact<IOlympicData>
columnDefs={columnDefs}
defaultColDef={defaultColDef}
rowModelType={"serverSide"}
suppressServerSideFullWidthLoadingRow={true}
cacheBlockSize={5}
maxBlocksInCache={0}
rowBuffer={0}
maxConcurrentDatasourceRequests={1}
blockLoadDebounceMillis={200}
onGridReady={onGridReady}
/>
</div>
</div>
</AgGridProvider>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(
<StrictMode>
<GridExample />
</StrictMode>,
);
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.
("use client");
import React, {
useCallback,
useMemo,
useRef,
useState,
StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
ColDef,
ColGroupDef,
GridApi,
GridOptions,
GridReadyEvent,
IServerSideDatasource,
IServerSideGetRowsRequest,
ModuleRegistry,
RowModelType,
enableDevValidations,
} from "ag-grid-community";
import {
RowGroupingModule,
ServerSideRowModelModule,
} from "ag-grid-enterprise";
import CustomLoadingCellRenderer from "./customLoadingCellRenderer.tsx";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
const modules = [ServerSideRowModelModule, RowGroupingModule];
const getServerSideDatasource: (server: any) => IServerSideDatasource = (
server: any,
) => {
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);
},
};
};
const getFakeServer: (allData: any[]) => any = (allData: 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 GridExample = () => {
const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
const [columnDefs, setColumnDefs] = useState<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 },
]);
const defaultColDef = useMemo<ColDef>(() => {
return {
loadingCellRenderer: () => "",
minWidth: 75,
};
}, []);
const onGridReady = useCallback((params: GridReadyEvent) => {
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((resp) => resp.json())
.then((data: IOlympicData[]) => {
// 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);
});
}, []);
return (
<AgGridProvider modules={modules}>
<div style={containerStyle}>
<div style={gridStyle}>
<AgGridReact<IOlympicData>
columnDefs={columnDefs}
defaultColDef={defaultColDef}
rowModelType={"serverSide"}
cacheBlockSize={5}
maxBlocksInCache={0}
rowBuffer={0}
maxConcurrentDatasourceRequests={1}
blockLoadDebounceMillis={200}
suppressServerSideFullWidthLoadingRow={true}
onGridReady={onGridReady}
/>
</div>
</div>
</AgGridProvider>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(
<StrictMode>
<GridExample />
</StrictMode>,
);
import React from 'react';
export default () => {
return <img src="https://www.ag-grid.com/example-assets/loading.gif" />;
};
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.