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 {
createApp,
defineComponent,
onBeforeMount,
ref,
shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
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 "./customLoadingCellRendererVue";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
NumberEditorModule,
TextEditorModule,
TextFilterModule,
NumberFilterModule,
ServerSideRowModelModule,
]);
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,
};
},
};
}
const VueExample = defineComponent({
template: `
<div style="height: 100%">
<div style="height: 100%; padding-top: 25px; box-sizing: border-box">
<ag-grid-vue
style="width: 100%; height: 100%;"
@grid-ready="onGridReady"
:columnDefs="columnDefs"
:defaultColDef="defaultColDef"
:loadingCellRenderer="loadingCellRenderer"
:loadingCellRendererParams="loadingCellRendererParams"
:rowModelType="rowModelType"
:cacheBlockSize="cacheBlockSize"
:maxBlocksInCache="maxBlocksInCache"
:rowData="rowData"></ag-grid-vue>
</div>
</div>
`,
components: {
"ag-grid-vue": AgGridVue,
CustomLoadingCellRenderer,
},
setup(props) {
const gridApi = shallowRef<GridApi<IOlympicData> | null>(null);
const columnDefs = ref<ColDef[]>([
{ field: "id" },
{ field: "athlete", width: 150 },
{ field: "age" },
{ field: "country" },
{ field: "year" },
{ field: "sport" },
{ field: "gold" },
{ field: "silver" },
{ field: "bronze" },
]);
const defaultColDef = ref<ColDef>({
editable: true,
flex: 1,
minWidth: 100,
filter: true,
});
const loadingCellRenderer = ref("CustomLoadingCellRenderer");
const loadingCellRendererParams = ref({
loadingMessage: "One moment please...",
});
const rowModelType = ref<RowModelType>("serverSide");
const cacheBlockSize = ref(20);
const maxBlocksInCache = ref(10);
const rowData = ref<IOlympicData[]>(null);
const onGridReady = (params: GridReadyEvent) => {
gridApi.value = params.api;
const updateData = (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);
};
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((resp) => resp.json())
.then((data) => updateData(data));
};
return {
gridApi,
columnDefs,
defaultColDef,
loadingCellRenderer,
loadingCellRendererParams,
rowModelType,
cacheBlockSize,
maxBlocksInCache,
rowData,
onGridReady,
};
},
});
const app = createApp(VueExample);
app.mount("#app");
export default {
template: `
<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>
`,
};
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 {
createApp,
defineComponent,
onBeforeMount,
ref,
shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
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 "./customLoadingCellRendererVue";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
NumberEditorModule,
TextEditorModule,
TextFilterModule,
NumberFilterModule,
ServerSideRowModelModule,
]);
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,
};
},
};
}
const VueExample = defineComponent({
template: `
<div style="height: 100%">
<div style="height: 100%; padding-top: 25px; box-sizing: border-box">
<ag-grid-vue
style="width: 100%; height: 100%;"
@grid-ready="onGridReady"
:columnDefs="columnDefs"
:defaultColDef="defaultColDef"
:loadingCellRenderer="loadingCellRenderer"
:loadingCellRendererParams="loadingCellRendererParams"
:rowModelType="rowModelType"
:cacheBlockSize="cacheBlockSize"
:serverSideInitialRowCount="serverSideInitialRowCount"
:rowData="rowData"></ag-grid-vue>
</div>
</div>
`,
components: {
"ag-grid-vue": AgGridVue,
CustomLoadingCellRenderer,
},
setup(props) {
const gridApi = shallowRef<GridApi<IOlympicData> | null>(null);
const columnDefs = ref<ColDef[]>([
{ field: "id" },
{ field: "athlete", width: 150 },
{ field: "age" },
{ field: "country" },
{ field: "year" },
{ field: "sport" },
{ field: "gold" },
{ field: "silver" },
{ field: "bronze" },
]);
const defaultColDef = ref<ColDef>({
editable: true,
flex: 1,
minWidth: 100,
filter: true,
});
const loadingCellRenderer = ref("CustomLoadingCellRenderer");
const loadingCellRendererParams = ref({
loadingMessage: "One moment please...",
});
const rowModelType = ref<RowModelType>("serverSide");
const cacheBlockSize = ref(10);
const serverSideInitialRowCount = ref(10);
const rowData = ref<IOlympicData[]>(null);
const onGridReady = (params: GridReadyEvent) => {
gridApi.value = params.api;
const updateData = (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);
};
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((resp) => resp.json())
.then((data) => updateData(data));
};
return {
gridApi,
columnDefs,
defaultColDef,
loadingCellRenderer,
loadingCellRendererParams,
rowModelType,
cacheBlockSize,
serverSideInitialRowCount,
rowData,
onGridReady,
};
},
});
const app = createApp(VueExample);
app.mount("#app");
export default {
template: `
<div class="ag-custom-loading-cell" style="padding-left: 10px; line-height: 25px;">
<i v-if=params.node.failedLoad class="fas fa-times"></i>
<span v-if=params.node.failedLoad>Data failed to load</span>
<div v-else> <i class="fas fa-spinner fa-pulse"></i> <span>{{ params.loadingMessage }}</span> </div>
</div>
`,
};
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 {
createApp,
defineComponent,
onBeforeMount,
ref,
shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
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();
}
ModuleRegistry.registerModules([ServerSideRowModelModule, RowGroupingModule]);
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 VueExample = defineComponent({
template: `
<div style="height: 100%">
<ag-grid-vue
style="width: 100%; height: 100%;"
@grid-ready="onGridReady"
:columnDefs="columnDefs"
:defaultColDef="defaultColDef"
:rowModelType="rowModelType"
:suppressServerSideFullWidthLoadingRow="true"
:cacheBlockSize="cacheBlockSize"
:maxBlocksInCache="maxBlocksInCache"
:rowBuffer="rowBuffer"
:maxConcurrentDatasourceRequests="maxConcurrentDatasourceRequests"
:blockLoadDebounceMillis="blockLoadDebounceMillis"
:rowData="rowData"></ag-grid-vue>
</div>
`,
components: {
"ag-grid-vue": AgGridVue,
},
setup(props) {
const gridApi = shallowRef<GridApi<IOlympicData> | null>(null);
const columnDefs = ref<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 = ref<ColDef>({
minWidth: 75,
});
const rowModelType = ref<RowModelType>("serverSide");
const cacheBlockSize = ref(5);
const maxBlocksInCache = ref(0);
const rowBuffer = ref(0);
const maxConcurrentDatasourceRequests = ref(1);
const blockLoadDebounceMillis = ref(200);
const rowData = ref<IOlympicData[]>(null);
const onGridReady = (params: GridReadyEvent) => {
gridApi.value = params.api;
const updateData = (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);
};
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((resp) => resp.json())
.then((data) => updateData(data));
};
return {
gridApi,
columnDefs,
defaultColDef,
rowModelType,
cacheBlockSize,
maxBlocksInCache,
rowBuffer,
maxConcurrentDatasourceRequests,
blockLoadDebounceMillis,
rowData,
onGridReady,
};
},
});
const app = createApp(VueExample);
app.mount("#app");
const gridOptions = {
suppressServerSideFullWidthLoadingRow: true,
}; Custom Loading Cells Copy Link
Set loadingCellRenderer on a column definition to customise its loading cells.
import {
createApp,
defineComponent,
onBeforeMount,
ref,
shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
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 "./customLoadingCellRendererVue";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([ServerSideRowModelModule, RowGroupingModule]);
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 VueExample = defineComponent({
template: `
<div style="height: 100%">
<ag-grid-vue
style="width: 100%; height: 100%;"
@grid-ready="onGridReady"
:columnDefs="columnDefs"
:defaultColDef="defaultColDef"
:rowModelType="rowModelType"
:cacheBlockSize="cacheBlockSize"
:maxBlocksInCache="maxBlocksInCache"
:rowBuffer="rowBuffer"
:maxConcurrentDatasourceRequests="maxConcurrentDatasourceRequests"
:blockLoadDebounceMillis="blockLoadDebounceMillis"
:suppressServerSideFullWidthLoadingRow="true"
:rowData="rowData"></ag-grid-vue>
</div>
`,
components: {
"ag-grid-vue": AgGridVue,
CustomLoadingCellRenderer,
},
setup(props) {
const gridApi = shallowRef<GridApi<IOlympicData> | null>(null);
const columnDefs = ref<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 = ref<ColDef>({
loadingCellRenderer: () => "",
minWidth: 75,
});
const rowModelType = ref<RowModelType>("serverSide");
const cacheBlockSize = ref(5);
const maxBlocksInCache = ref(0);
const rowBuffer = ref(0);
const maxConcurrentDatasourceRequests = ref(1);
const blockLoadDebounceMillis = ref(200);
const rowData = ref<IOlympicData[]>(null);
const onGridReady = (params: GridReadyEvent) => {
gridApi.value = params.api;
const updateData = (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);
};
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((resp) => resp.json())
.then((data) => updateData(data));
};
return {
gridApi,
columnDefs,
defaultColDef,
rowModelType,
cacheBlockSize,
maxBlocksInCache,
rowBuffer,
maxConcurrentDatasourceRequests,
blockLoadDebounceMillis,
rowData,
onGridReady,
};
},
});
const app = createApp(VueExample);
app.mount("#app");
export default {
template: `
<img src="https://www.ag-grid.com/example-assets/loading.gif">
`,
};
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.