gp-grid-logo
API Reference

Data Sources

Connect the Angular grid to client-side arrays or server-side endpoints, and plug streaming, paginated, or fully remote data flows into gp-grid with ease.

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

OptionDefaultDescription
getFieldValueCustom accessor for nested properties
useWorkertrueUse 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.

my-grid.component.ts
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

DataSource Interface

All data sources implement the DataSource<TData> interface. Both createClientDataSource and createServerDataSource return objects that conform to it; MutableDataSource<TData> extends it with mutation methods.

interface DataSource<TData> {
  readonly loadMode?: "all" | "paginated";
  query(request: DataSourceRequest): Promise<DataSourceResponse<TData>>;
  destroy?(): void;
  moveRow?(fromIndex: number, toIndex: number): void;
}
MemberRequiredDescription
loadModeNo"all" (default for client data sources) or "paginated" (default for server). Controls whether the grid requests every row at once or only the visible window via range.startRow / range.endRow.
queryYesAsync function returning { rows, totalRows } for the requested range, sort, and filter. Called by the grid whenever the visible window, sort, or filter changes.
destroyNoCleanup hook for releasing workers, sockets, or other resources. Called when the data source is detached from the grid.
moveRowNoCalled on row drag-end. Implement it to update the underlying data; the grid does not mutate data on its own.

Server Data Source

For server-side sorting, filtering, and windowed row loading:

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 { range, sort, filter } = request;

    const response = await fetch("/api/data", {
      method: "POST",
      body: JSON.stringify({
        startRow: range.startRow,
        endRow: range.endRow,
        sortBy: sort,
        filters: filter,
      }),
    });
    const result = await response.json();
    return { rows: result.data, totalRows: result.total };
  });
}

Pass { loadMode: "all" } as a second argument if the server returns the entire result set in one call. The default is "paginated" — the grid only requests the row window currently in view via range.startRow / range.endRow.

const dataSource = createServerDataSource(queryFn, { loadMode: "paginated" });

Request / Response

interface DataSourceRequest {
  range: {
    startRow: number; // First row index to fetch (0-indexed, inclusive)
    endRow: number;   // First row index after the range (exclusive)
  };
  sort?: SortModel[];
  filter?: FilterModel;
}

interface DataSourceResponse<TData> {
  rows: TData[];
  totalRows: number;
}

Choosing a Data Source

ScenarioRecommended
Small dataset (under 10k rows)rowData input
Large dataset, client-sidecreateClientDataSource
Editable data with CRUD, inside a componentprovideGridData + injectGridData
Editable data, outside DI (service, shared utility)createGridData
Editable data, shared singleton across unrelated componentscreateMutableClientDataSource
Server-side operationscreateServerDataSource

On this page