Skip to content

Pagination Remote

This example shows how to use pagination with remote data in the grid. To enable pagination you’ll need to use the PaginationPlugin. This plugin provides the necessary functionality to paginate data within the grid.

Source code
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
AdvanceFilterPlugin,
PaginationPlugin,
ColumnStretchPlugin,
RowOddPlugin,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import { createRemotePaginationColumns } from './pagination.remote.utils';
import { startRemotePagination } from './pagination.remote.bootstrap';
defineCustomElements();
export function load(parentSelector: string) {
const parent = document.querySelector(parentSelector);
if (!parent) throw new Error('Remote pagination container not found.');
const shell = document.createElement('section');
shell.className = 'pagination-remote-demo';
Object.assign(shell.style, {
minHeight: '0',
height: '360px',
display: 'flex',
flexDirection: 'column',
flex: '1 1 auto',
});
parent.appendChild(shell);
const lifetime = new AbortController();
const status = document.createElement('p');
status.setAttribute('role', 'status');
status.textContent = 'Starting demo backend…';
shell.appendChild(status);
let grid: HTMLRevoGridElement | undefined;
void startRemotePagination(lifetime.signal)
.then((pagination) => {
if (lifetime.signal.aborted) return;
grid = document.createElement('revo-grid');
grid.className = 'overflow-hidden cell-border';
grid.style.cssText = 'height: 100%; min-height: 0; flex: 1 1 0;';
grid.columns = createRemotePaginationColumns();
grid.filter = true;
grid.stretch = 'all';
Object.assign(grid, { pagination });
grid.plugins = [
PaginationPlugin,
AdvanceFilterPlugin,
ColumnStretchPlugin,
RowOddPlugin,
];
grid.theme = currentTheme().isDark() ? 'darkMaterial' : 'material';
grid.hideAttribution = true;
shell.appendChild(grid);
grid.source = [];
status.remove();
})
.catch((error) => {
if (lifetime.signal.aborted) return;
status.setAttribute('role', 'alert');
status.textContent = error.message;
});
return () => {
lifetime.abort();
grid?.remove();
shell.remove();
};
}

The demo uses native fetch() against an isolated MSW backend with 102 deterministic records. The backend owns paging, filtering, sorting, and the filtered count. The HTTP client, bootstrap, routes, and query service are separate source tabs, shared by all four framework variants.

The bootstrap awaits worker readiness before mounting the grid and disposes its session when the framework unmounts. The loader forwards the optional cancellation signal and preserves HTTP status codes when reporting errors to the grid.

The MSW runner is demo infrastructure in this workspace, not a required product dependency. In your application, use your own API URL with the client loader. To reuse the simulation in another demo host, include the shared runner, serve the MSW worker as mockServiceWorker.js beneath that host’s configured base URL, and await startup before issuing requests. The demos and docs hosts already serve the worker matching their installed MSW version. Use HTTPS or localhost.

The demo loader sends the canonical filterAst when supplied, with fallbacks to the full multi-condition model and legacy single-condition collection. Backend filter evaluation uses the supported public filter helpers.

loadData keeps the original fourth argument for compatibility and passes the complete advanced filter model as an optional fifth argument:

grid.pagination = {
itemsPerPage: 50,
initialPage: 0,
total: 0,
loadData: async (
skip,
take,
order,
singleConditionFilters,
multiConditionFilters,
_quickFilter,
_temporalContext,
_filterAst,
_executionContext,
context, // Optional tenth argument: { signal }.
) => {
return fetch('/api/orders', {
method: 'POST',
signal: context?.signal,
body: JSON.stringify({
skip,
take,
order,
filters: multiConditionFilters,
}),
}).then(response => response.json());
},
};

signal: context?.signal forwards the optional cancellation signal to fetch, so the transport can stop when the read is no longer needed. See loader context and cancellation.

Use multiConditionFilters for new integrations because it preserves every condition and same-column relation. singleConditionFilters contains only the first condition for each column.

The existing pagination.loadData(...) callback now runs through the shared Query engine automatically. Keep its existing parameters and result shape. An optional cancellation context { signal } is appended as argument ten, after the existing execution context. Existing callbacks may ignore it. Local pagination without loadData continues to use the client source.

For linked views, create an app/session scope using createDataQueryContext from @revolist/revogrid-pro and supply the same query: { source } inside each pagination configuration. Ordinary configurations need no setup. The optional query object also supports reconcile, onStatus, resource and snapshot consistency.

Apply canonical accepted writes and reconcile application proposals before calling source.accepted(). Use source.invalidate() for external server changes. Assign normal pagination configuration with a new source when tenant/permission scope changes; old rows/counts clear immediately and pagination returns to the first page. Remote hydration preserves history and emits no new save. Each grid receives detached editable rows; reconciliation runs outside the shared result cache.

See Server Data Queries for polling, shared views, caching, retries, lifecycle and the complete configuration types and defaults. Your existing loader callback stays unchanged; add options inside query.

The query concepts are explained individually:

For an application-owned query library/cache, set query: false. See bypassing Query for lifecycle behavior and the difference from query: { enabled: false }.