Server-Side Data
Pass a dataSource instead of a data array to delegate sorting, filtering, and pagination to your server.
Live Demo
The demo above uses Radix UI for styling. To see this feature with your framework's design system (Fluent UI, Material UI, Vuetify, PrimeNG, etc.), click "Open in online demo" below the demo.
Quick Example
- React
- Angular
- Vue
- Vanilla JS
import { OGrid } from '@alaarab/ogrid-react-radix';
import type { IDataSource, IFetchParams, IPageResult } from '@alaarab/ogrid-react-radix';
interface Employee {
id: number;
name: string;
department: string;
salary: number;
}
const dataSource: IDataSource<Employee> = {
async fetchPage(params: IFetchParams): Promise<IPageResult<Employee>> {
const query = new URLSearchParams({
page: String(params.page),
pageSize: String(params.pageSize),
...(params.sort && {
sortField: params.sort.field,
sortDir: params.sort.direction,
}),
});
const res = await fetch(`/api/employees?${query}`);
return res.json(); // { items: Employee[], totalCount: number }
},
};
function App() {
return (
<OGrid
columns={columns}
dataSource={dataSource}
getRowId={(e) => e.id}
defaultPageSize={25}
/>
);
}
The OGrid component has the same props across all React UI packages. To switch, just change the import:
- Radix (lightweight, default):
from '@alaarab/ogrid-react-radix' - Fluent UI (Microsoft 365 / SPFx):
from '@alaarab/ogrid-react-fluent'— wrap in<FluentProvider> - Material UI (MUI v7):
from '@alaarab/ogrid-react-material'— wrap in<ThemeProvider>
import { Component } from '@angular/core';
import { OGridComponent, type IDataSource, type IFetchParams, type IPageResult } from '@alaarab/ogrid-angular-material';
interface Employee {
id: number;
name: string;
department: string;
salary: number;
}
const dataSource: IDataSource<Employee> = {
async fetchPage(params: IFetchParams): Promise<IPageResult<Employee>> {
const query = new URLSearchParams({
page: String(params.page),
pageSize: String(params.pageSize),
...(params.sort && {
sortField: params.sort.field,
sortDir: params.sort.direction,
}),
});
const res = await fetch(`/api/employees?${query}`);
return res.json();
},
};
@Component({
standalone: true,
imports: [OGridComponent],
template: `<ogrid [props]="gridProps" />`
})
export class GridComponent {
gridProps = {
columns: columns,
dataSource,
getRowId: (e: Employee) => e.id,
defaultPageSize: 25,
};
}
Same component API across Angular packages. To switch, just change the import:
- Radix (CDK):
from '@alaarab/ogrid-angular-radix'(default, lightweight) - Angular Material:
from '@alaarab/ogrid-angular-material' - PrimeNG:
from '@alaarab/ogrid-angular-primeng'
All components are standalone — no NgModule required.
<script setup lang="ts">
import { OGrid, type IDataSource, type IFetchParams, type IPageResult } from '@alaarab/ogrid-vue-vuetify';
interface Employee {
id: number;
name: string;
department: string;
salary: number;
}
const dataSource: IDataSource<Employee> = {
async fetchPage(params: IFetchParams): Promise<IPageResult<Employee>> {
const query = new URLSearchParams({
page: String(params.page),
pageSize: String(params.pageSize),
...(params.sort && {
sortField: params.sort.field,
sortDir: params.sort.direction,
}),
});
const res = await fetch(`/api/employees?${query}`);
return res.json();
},
};
const gridProps = {
columns,
dataSource,
getRowId: (e: Employee) => e.id,
defaultPageSize: 25,
};
</script>
<template>
<OGrid :gridProps="gridProps" />
</template>
Same component API across Vue packages. To switch, just change the import:
- Radix (Headless UI):
from '@alaarab/ogrid-vue-radix'(default, lightweight) - Vuetify:
from '@alaarab/ogrid-vue-vuetify'— wrap in<v-app>for theming - PrimeVue:
from '@alaarab/ogrid-vue-primevue'
import { OGrid } from '@alaarab/ogrid-js';
import '@alaarab/ogrid-js/styles';
const grid = new OGrid(document.getElementById('grid'), {
columns: columns,
dataSource: {
async fetchPage({ page, pageSize, sort, filters }) {
const query = new URLSearchParams({
page: String(page),
pageSize: String(pageSize),
...(sort && {
sortField: sort.field,
sortDir: sort.direction,
}),
});
const res = await fetch(`/api/employees?${query}`);
return res.json(); // { items: Employee[], totalCount: number }
},
},
getRowId: (e) => e.id,
pageSize: 25,
});
The grid fetches the first page on mount and re-fetches whenever page, sort, or filters change.
IDataSource<T> Reference
interface IDataSource<T> {
fetchPage(params: IFetchParams): Promise<IPageResult<T>>;
fetchFilterOptions?(field: string): Promise<string[]>;
searchPeople?(query: string): Promise<UserLike[]>;
getUserByEmail?(email: string): Promise<UserLike | undefined>;
}
| Method | Required | Description |
|---|---|---|
fetchPage | Yes | Fetches a page of data. Called on page/sort/filter changes. |
fetchFilterOptions | No | Returns options for multiSelect filters with optionsSource: 'api'. |
searchPeople | No | Searches people for people-type column filters. |
getUserByEmail | No | Resolves a user by email (for restoring saved people filters). |
IFetchParams
interface IFetchParams {
page: number; // 1-indexed
pageSize: number;
sort?: { field: string; direction: 'asc' | 'desc' };
filters: IFilters; // See Types reference for FilterValue shapes
}
IPageResult<T>
interface IPageResult<T> {
items: T[];
totalCount: number; // drives pagination controls
}
Filter Options & People Search
const dataSource: IDataSource<Employee> = {
async fetchPage(params) { /* ... */ },
async fetchFilterOptions(field: string): Promise<string[]> {
const res = await fetch(`/api/employees/filter-options/${field}`);
return res.json(); // ['Engineering', 'Marketing', 'Sales']
},
async searchPeople(query: string): Promise<UserLike[]> {
const res = await fetch(`/api/people/search?q=${query}`);
return res.json();
},
async getUserByEmail(email: string): Promise<UserLike | undefined> {
const res = await fetch(`/api/people/${email}`);
if (!res.ok) return undefined;
return res.json();
},
};
Error Handling & Loading
<OGrid
columns={columns}
dataSource={dataSource}
getRowId={(e) => e.id}
onError={(error) => {
console.error('Grid fetch failed:', error);
showToast('Failed to load data.');
}}
/>
The grid manages loading state automatically. You can also control it via gridRef.current?.setLoading(true).
Client-Side vs. Server-Side
Client-side (data) | Server-side (dataSource) | |
|---|---|---|
| Data location | All rows in memory | Fetched per page |
| Sorting / Filtering | In-memory by the grid | Your server handles it |
| Best for | < 10,000 rows | Large datasets, real-time data |