Data Sources
Client-side and server-side data handling in Angular
Data Sources
gp-grid provides the same data source abstractions across bindings. Angular consumers have two ergonomic entry points on top of the core factories: an injectable GridDataService for component-scoped DI, and a plain createGridData() helper for when DI is not a fit.
Basic Usage with rowData
The simplest approach is to pass an array through the rowData input:
@Component({
selector: "app-my-grid",
standalone: true,
imports: [GpGridComponent],
template: `
<gp-grid [columns]="columns" [rowData]="data" [rowHeight]="36" /> // [!code highlight]
`,
})
export class MyGridComponent {
data = [
{ id: 1, name: "Giovanni" },
{ id: 2, name: "Luca" },
];
}This internally creates a client data source.
Client Data Source
For more control — custom field accessors, disabling the Web Worker, etc. — use createClientDataSource:
import { GpGridComponent, createClientDataSource } from "@gp-grid/angular";
@Component({
standalone: true,
imports: [GpGridComponent],
template: `<gp-grid [columns]="columns" [dataSource]="dataSource" [rowHeight]="36" />`,
})
export class MyGridComponent {
dataSource = createClientDataSource(largeDataset, {
useWorker: true,
});
}Options
| Option | Default | Description |
|---|---|---|
getFieldValue | – | Custom accessor for nested properties |
useWorker | true | Use a Web Worker for sorting large datasets |
Mutable Data Source
For CRUD operations and real-time updates inside an Angular component, prefer the injectable GridDataService via provideGridData() / injectGridData(). It wraps createMutableClientDataSource with automatic cleanup on component destroy and gives every @Component its own isolated instance.
import { Component } from "@angular/core";
import {
GpGridComponent,
provideGridData,
injectGridData,
type ColumnDefinition,
} from "@gp-grid/angular";
interface Person {
id: number;
name: string;
age: number;
}
@Component({
selector: "app-my-grid",
standalone: true,
imports: [GpGridComponent],
providers: [
provideGridData<Person>({
getRowId: (row) => row.id,
initialData: seedRows,
debounceMs: 50,
}),
],
template: `
<gp-grid [columns]="columns" [dataSource]="grid.dataSource" [rowHeight]="36" />
<button (click)="addOne()">Add</button>
<button (click)="renameFirst()">Rename first</button>
`,
})
export class MyGridComponent {
protected readonly grid = injectGridData<Person>();
columns: ColumnDefinition[] = [
{ field: "id", cellDataType: "number", width: 80 },
{ field: "name", cellDataType: "text", width: 200 },
{ field: "age", cellDataType: "number", width: 100 },
];
addOne(): void {
this.grid.addRows([{ id: Date.now(), name: "Mario", age: 30 }]);
}
renameFirst(): void {
this.grid.updateCell(1, "name", "Maria");
}
}provideGridData() must be registered in the component's providers array, not in a module or a parent component. Registering it higher up would silently share one data source between every child — which is usually a bug. One grid component, one provideGridData() call.
Function-based alternative: createGridData
When you want a mutable data source without going through DI — for example in a service that manages several grids, or in a standalone utility — reach for createGridData(). It returns the same API object shape, minus the automatic cleanup:
import { createGridData } from "@gp-grid/angular";
@Component({ /* ... */ })
export class MyGridComponent implements OnDestroy {
protected readonly grid = createGridData<Person>(seedRows, {
getRowId: (row) => row.id,
});
ngOnDestroy(): void {
this.grid.clear();
}
}Unlike GridDataService, createGridData() does not dispose of the underlying data source when the component is destroyed. Call clear() (or tear it down yourself) in ngOnDestroy to avoid leaking workers and subscriptions.
Escape hatch: raw factory
Reach for the raw createMutableClientDataSource factory only when neither path above fits — module-level singletons, shared data sources used by multiple unrelated components, or non-Angular code. In a component, always prefer provideGridData / createGridData.
// Outside a component (e.g. a shared module)
import { createMutableClientDataSource } from "@gp-grid/angular";
export const sharedDataSource = createMutableClientDataSource(seed, {
getRowId: (row) => row.id,
});Transaction System
The mutable data source batches mutations through an internal transaction system:
- Automatic batching — operations within the debounce window collapse into a single transaction, minimising re-renders.
- Optimistic processing — mutations are queued asynchronously so the UI stays responsive under heavy churn.
- Flush control — call
flushTransactions()to force immediate processing (useful before navigating away or running assertions).
await this.grid.flushTransactions();Options
interface GridDataOptions<TData> {
initialData: TData[]; // Required: initial rows
getRowId: (row: TData) => RowId; // Required: unique ID accessor
debounceMs?: number; // Batch window (default: 50)
useWorker?: boolean; // Worker sort (default: true)
parallelSort?: ParallelSortOptions | false;
}GridDataService Interface
The injected service exposes the same surface as the React hook and Vue composable, plus the dataSource itself:
class GridDataService<TData> {
readonly dataSource: MutableDataSource<TData>;
addRows(rows: TData[]): void;
removeRows(ids: RowId[]): void;
updateRow(id: RowId, data: Partial<TData>): void;
updateCell(id: RowId, field: string, value: CellValue): void;
clear(): void;
getRowById(id: RowId): TData | undefined;
getTotalRowCount(): number;
flushTransactions(): Promise<void>;
}Use Cases
The mutable data source is ideal for:
- Real-time dashboards — live feeds with frequent updates
- Streaming data — WebSocket or SSE pipelines
- User editing — forms and inline editing with immediate feedback
- Bulk operations — imports, mass updates, batch delete
Server Data Source
For server-side sorting, filtering, and pagination:
import { GpGridComponent, createServerDataSource } from "@gp-grid/angular";
@Component({
standalone: true,
imports: [GpGridComponent],
template: `<gp-grid [columns]="columns" [dataSource]="dataSource" [rowHeight]="36" />`,
})
export class MyGridComponent {
dataSource = createServerDataSource(async (request) => {
const response = await fetch("/api/data", {
method: "POST",
body: JSON.stringify(request),
});
const result = await response.json();
return { rows: result.data, totalRows: result.total };
});
}Request / Response
interface DataSourceRequest {
pagination: { pageIndex: number; pageSize: number };
sort?: SortModel[];
filter?: FilterModel;
}
interface DataSourceResponse<TData> {
rows: TData[];
totalRows: number;
}Choosing a Data Source
| Scenario | Recommended |
|---|---|
| Small dataset (under 10k rows) | rowData input |
| Large dataset, client-side | createClientDataSource |
| Editable data with CRUD, inside a component | provideGridData + injectGridData |
| Editable data, outside DI (service, shared utility) | createGridData |
| Editable data, shared singleton across unrelated components | createMutableClientDataSource |
| Server-side operations | createServerDataSource |