@reforgium/data-grid
v3.2.8
Published
reforgium DataGrid component
Downloads
665
Maintainers
Readme
@reforgium/data-grid
High-performance data grid for Angular 18+.
@reforgium/data-grid provides a flexible and performant component for displaying large tabular datasets.
It focuses on smooth scrolling, predictable layout, and full control over rendering via templates and signals.
Designed for real-world datasets, not demo tables.
Features
- Horizontal and vertical scrolling
- Virtual row rendering (smooth, no jumps)
- Infinity scroll (loads data when reaching the bottom)
- Jitter-free fixed (sticky) columns
- Two-line text clamp (header + body) with ellipsis
- Declarative column DSL (
<re-dg-column>) [NEW in 2.0.0] - Column expanders (hidden columns via toggler)
- Scrollable overlay scrollbar
- Pinned rows (top and bottom)
- Custom templates for headers, cells, pinned rows, icons
- Skeleton loading rows for pagination/infinity [NEW in 2.0.0]
- Row selection (single / multi)
- Signals-based API (
signal()first) - Paginator component [NEW in 1.1.0]
- Column manager dropdown [NEW in 2.0.0]
Requirements
- Angular >=18.0.0
- RxJS is not required by this package.
Installation
npm install @reforgium/data-gridimport { DataGrid } from '@reforgium/data-grid';
import { DataGridPaginator } from '@reforgium/data-grid/paginator';
import { DataGridColumnManager } from '@reforgium/data-grid/column-manager';
@Component({ imports: [DataGrid, DataGridPaginator] })
export class SomeComponent {}<re-data-grid
mode="infinity"
[data]="users"
[columns]="columns"
[pageSize]="50"
[loading]="loading"
(pageChange)="loadMore($event)"
/>For small and medium lists, data remains the simplest contract.
For server-paged lists, you can now pass a page-oriented source instead:
import type { GridPagedDataSource } from '@reforgium/data-grid';
type User = { id: number; name: string };
declare const usersSource: GridPagedDataSource<User>;<re-data-grid mode="infinity" [source]="usersSource" [columns]="columns" [pageSize]="20" />In source mode, the grid keeps its own internal page buffer for infinity scrolling, so the parent does not need to maintain one ever-growing accumulated data[].
Important:
- The grid does not auto-fetch on mount in
sourcemode. - The parent should first apply filters / route params / query state and then call the source explicitly (
fetch(...),updatePage(0), etc.). - After the source is initialized, the grid prefers
source.loadPage(...)for explicit page results and usessource.updatePage(...)only as the deprecated compatibility fallback. - If the source exposes
updatePageSize(...), the grid uses it when the page size changes. - If the source exposes
sort,updateSort(...), orupdateSorts(...), the grid keeps sort state in sync and can delegate user sorting directly to the source. - Infinity buffering uses
prefetchMode: 'sequential'by default.prefetchMode: 'parallel'requires a truly statelessloadPage(...); keepPagedQueryStoreand other latest-wins stateful sources sequential unless they expose that capability. - Set
prefetchMode: 'parallel'only for sources that provide explicit page-local results throughloadPage(...).
Request ownership:
- Without
source, paginator actions andrequestPage()emitpageChange; the parent owns fetching and updates[data]itself. - With
source, paginator actions,requestPage(),requestPageSize(), andretryPage()use the source request path and do not emitpageChange. Do not also fetch from(pageChange)when[source]is bound. - A page-size change calls
updatePageSize(size)once when that optional capability exists. The source owns resetting to and loading page zero as part of that operation. - A sort starts a new dataset generation. It aborts or discards stale stateless page results; stateful
updatePage()sources must keep their own latest-wins behavior. Page actions received while a source sort request is active are ignored.
PagedQueryStore from @reforgium/statum fits this contract directly, so [source]="store" is enough for the common server-table case.
Configuration
Global defaults provider
You can override default input values for all grid instances via DI:
import { provideDataGridDefaults } from '@reforgium/data-grid/config';
export const appConfig: ApplicationConfig = {
providers: [
provideDataGridDefaults({
mode: 'pagination',
hasIndexColumn: true,
resizable: true,
pageSize: 50,
translations: {
indexColumnHeader: 'No.',
},
}),
],
};Use the /config entry point in application bootstrap code. It contains only the DI token and provider defaults,
so importing global configuration does not pull the grid renderer into the initial bundle. The root export remains
available for compatibility.
Supported default fields:
modehasIndexColumnselectionpageSizeresizablerowHeightheaderHeightheightvirtualBufferloadingModedeferContentdeferHeaderdeferPinneddeferCellspageStartFromZerotranslationsdebounce
translations supports: emptyState, itemsPerPageLabel,
extPageLabel, prevPageLabel, indexColumnHeader`.
Global header text resolver
If your app keeps column headers as i18n keys, you can resolve them globally via DI without wiring headerTemplate for every table.
provideDataGridHeaderTextResolver(...)registers(text, ctx) => string | Signal<string>- The resolver applies to plain column headers and header-group titles
headerTemplate/titleTemplatestill win for markup, but receive already-resolved text in$implicitprovideDataGridHeaderTextResolverWithParent(...)is available for advanced parent-resolver composition
import { inject } from '@angular/core';
import { provideDataGridHeaderTextResolver } from '@reforgium/data-grid';
import { LangService } from '@reforgium/presentia';
export const appConfig: ApplicationConfig = {
providers: [
provideDataGridHeaderTextResolver(() => {
const lang = inject(LangService);
return (text) => (text.includes('.') ? lang.observe(text) : text);
}),
],
};columns = [
{ key: 'name', header: 'users.columns.name' },
{ key: 'email', header: 'users.columns.email' },
];Type registries (global + local)
You can register type-based transformers and renderers globally via DI.
provideDataGridTypeTransformers(...)registerstype -> (row, ctx) => valueprovideDataGridTypeRenderers(...)registerstype -> TemplateRefreDataGridTypeCell="..."registers an instance-local renderer (only for that grid instance)
import { provideDataGridTypeTransformers } from '@reforgium/data-grid';
export const routes: Routes = [
{
path: 'users',
providers: [
provideDataGridTypeTransformers({
money: (_row, ctx) => `$${Number(ctx.value ?? 0).toLocaleString('en-US')}`,
}),
],
loadComponent: () => import('./users.page').then((m) => m.UsersPage),
},
];columns = [{ key: 'salary', header: 'Salary', type: 'money' }];<re-data-grid [data]="users" [columns]="columns">
<!-- Local renderer overrides global renderer for this grid only -->
<ng-template reDataGridTypeCell="money" let-value="value" let-row="row">
<b>{{ value }}</b> <small>{{ row.status }}</small>
</ng-template>
</re-data-grid>Renderer precedence:
column.renderTemplate- local
reDataGridTypeCell - DI
provideDataGridTypeRenderers(...) - built-in renderer (
date,number,index, ...) - default text renderer
Value transform precedence:
column.transformer(row, ctx)- DI
provideDataGridTypeTransformers(...) - default value pipeline
ctx includes: value, col, index, type.
Type/value callback context (ctx) and pinned rows:
value(row, ctx)receives:ctx.col,ctx.index,ctx.isPinnedtransformer(row, ctx)receives:ctx.value,ctx.col,ctx.index,ctx.type,ctx.isPinned- Cell template contexts (including
reDataGridTypeCell,reDataGridCell) exposeisPinned
columns = [
{
key: 'code',
header: 'Code',
value: (row, ctx) => (ctx.isPinned ? 'PINNED' : row.code),
},
{
key: 'salary',
header: 'Salary',
type: 'money',
transformer: (row, ctx) => (ctx.isPinned ? `PIN: ${ctx.value}` : ctx.value),
},
];<ng-template reDataGridTypeCell="money" let-value="value" let-isPinned="isPinned">
<b>{{ value }}</b>
@if (isPinned) {
<small>PIN</small>
}
</ng-template>Inputs
| Parameter | Type | Default | Description |
|---------------------|-----------------------------------------------------|-----------------------|----------------------------------------------------------------|
| data | T[] | [] | Data array to render. |
| source | GridPagedDataSource<T> \| null | null | Page-oriented source for pagination and infinity flows. |
| columns | GridColumn<T>[] | [] | Programmatic column configuration. |
| headerGroups | GridHeaderGroup<T>[] | [] | Optional two-level header groups. |
| pinnedRows | GridPinnedRow<T>[] | [] | Top and bottom pinned rows. |
| isRowSticky | (row: T, index: number) => boolean | undefined | Predicate for sticky data rows. |
| isRowDisabled | (row: T, index: number) => boolean | undefined | Predicate for disabled rows. |
| getRowTemplate | (row: T, index: number) => TemplateRef \| null | undefined | Optional custom row template resolver. |
| sortMode | 'single' \| 'multi' | 'single' | Sorting mode for header actions. |
| pageSize | number | 20 | Page size for pagination and infinity modes. |
| pageStartFromZero | boolean | true | Whether page indices start from zero. |
| hasIndexColumn | boolean | false | Whether to add the index column. |
| selection | GridSelection<T> | { mode: 'none' } | Row selection configuration. |
| selectedKeys | ReadonlyArray<GridSelectionValue<T>> \| undefined | undefined | Controlled selection values. |
| selectionPolicy | 'preserve-unloaded' \| 'loaded-only' | 'preserve-unloaded' | Cross-page selection reconciliation policy. |
| rowHeight | number | 40 | Fixed data-row height in pixels. |
| virtualBuffer | number | 8 | Extra virtual rows above and below the viewport. |
| lockVerticalScroll | boolean | false | Locks vertical while retaining horizontal scroll. |
| height | number \| 'full' \| 'default' | 'default' | Grid height in pixels, full height, or the configured default. |
| loading | boolean | false | Fallback loading state when no source is provided. |
| loadingMode | 'spinner' \| 'skeleton' | 'spinner' | Loading presentation. |
| deferContent | boolean | true | Defers main-content rendering. |
| deferHeader | boolean | false | Defers header rendering. |
| deferPinned | boolean | false | Defers pinned-row rendering. |
| deferCells | boolean | false | Defers cell-content rendering. |
| rowKey | DataKey<T> \| ((item: T) => string \| number) | undefined | Stable row identity property or resolver. |
When selection mode is 'single' or 'multi', provide a key (data property). Use defaultSelectedKeys for uncontrolled initial state. defaultSelected remains supported but is deprecated.
Row identity and virtual rendering:
rowKeyis the Angular render key for virtual data rows. Supply a unique stable property or resolver whenever rows can be reordered, replaced, or contain stateful templates.- Without
rowKey, rows are tracked by absolute dataset index. This prevents stale slot state, but a reorder recreates affected row views rather than preserving them by record identity. - Column
trackis accepted for compatibility but deprecated and ignored by the renderer; userowKey.
Selection ownership and page policy:
- Leave
selectedKeysunset for uncontrolled selection. The grid keeps its local state and emits bothselectChangeandselectedKeysChange. - Bind
selectedKeysand handleselectedKeysChangefor controlled selection. The grid emits a proposal and immediately restores the input value until the consumer supplies the next value. selectionPolicydefaults to'preserve-unloaded', so selected keys survive pagination, infinity page buffering, and background prefetch even when their rows are not currently rendered.- Set
selectionPolicy="loaded-only"only when each loaded collection is the complete selection scope; it prunes selected values absent from the current loaded rows. selectAllLoaded()and the header checkbox operate on loaded/current-page rows only. They add or remove those values while preserving selections from other pages; server-wide selection is intentionally not modeled by this API.- For compile-time correlation between the configured key and selected values, declare configuration as
GridSelectionByKey<Row, 'id'>; the legacyGridSelection<Row>remains permissive for compatibility.
Data source guidance:
- Use
datawhen the parent already owns the full rendered collection. - Use
sourcefor server-driven pagination or infinity flows where the parent should expose only the current page. - In
mode="infinity"+source, the grid usestotalElementsfor scroll height when available and stores loaded page chunks internally instead of forcing the parent to append rows into one large array. GridPagedDataSource.totalElementsis optional for source-driven infinity and pagination flows.- Mutable infinity sources should expose
versionand increment it after replacement/filter/sort changes. An immutable source may setimmutable: trueand omitversion, but it must never replace data in place; assign a new source instance for a new dataset. - In
mode="infinity", iftotalElementsis omitted, the grid keeps requesting pages until it receives a short page (items.length < pageSize) and then treats that page as the end of the dataset. - In
mode="pagination", iftotalElementsis omitted, the grid exposes one optimistic extra page while the current page is full. The final size becomes known only after a short page is returned. - In
sourcemode, initialize the source from the parent after all filters / params are ready; the grid does not perform an automatic first fetch on mount. GridPagedDataSource.erroris optional and can be used by consumers that want to project source-level error UI near the grid.- When a request fails and the source does not expose
error, the grid emitssourceErrorwith its operation (page,page-size,sort,multi-sort, orprefetch) and applicable page/sort context. Abort and stale results do not emit errors. - Use
retryPage()to repeat the current source page (or pass a page explicitly). It follows the same source request path as user pagination; retry UI remains consumer-owned. GridPagedDataSource.sort,updateSort(...), andupdateSorts(...)let the grid use source-owned sort state instead of forcing a separate parent bridge.GridPagedDataSource.prefetchModedefaults tosequential; keep that default forPagedQueryStoreand similar latest-wins sources.- Use
prefetchMode: 'parallel'only when the source exposesloadPage(...); the explicit page result keeps concurrent responses page-local. - When
source.versionchanges, a new source is assigned, orresetSourceBuffer()is called, the grid invalidates its internal buffer immediately; grid-owned sort and page-size changes also invalidate it. Any in-flightloadPage()orupdatePage()responses that arrive after invalidation are discarded and do not overwrite the fresh buffer.
Feature lazy-loading behavior (runtime import()):
- Selection feature is loaded when
selection.mode !== 'none'. - Sticky feature is loaded when
isRowStickyis provided. - Tooltip feature is loaded when at least one column has
tooltip. - Overlay-scroll feature is loaded on first scroll lifecycle usage.
Loading behavior:
loadingMode="spinner": shows a centered blocking spinner only while the source has no renderable rows; background prefetch keeps existing rows interactive.loadingMode="skeleton"+mode="infinity"+loading=true: appends 4 skeleton rows at the end of the current data.loadingMode="skeleton"+mode="pagination"+loading=true+ empty data: renders 4 skeleton rows in the table body.
Sticky rows:
- Provide
isRowStickyto mark rows that should stick to the top during scroll. - You can customize the sticky row rendering with
reDataGridStickyRowtemplate.
<re-data-grid [data]="items" [columns]="columns" [isRowSticky]="isSticky">
<ng-template reDataGridStickyRow let-row let-index="index">
<div class="my-sticky-row">{{ index + 1 }}. {{ row.name }}</div>
</ng-template>
</re-data-grid>Row templates:
- Provide
getRowTemplateto select a custom row template per row. - Use
reDataGridRowas a default row template.
<re-data-grid [data]="items" [columns]="columns" [getRowTemplate]="rowTpl">
<ng-template reDataGridRow let-index="index">
<div class="my-row">{{ index + 1 }}. {{ row.name }}</div>
</ng-template>
</re-data-grid>Outputs
| Event | Type | Description | |
|--------------------|----------------------------------------|--------------------------------------------------------------------------------|-------------------|
| cellClick | GridCellClickEvent<T> | Emitted when a cell is clicked (includes native event) | [UPD in 2.0.0] |
| cellContext | GridCellContextEvent<T> | Emitted on cell context menu | [NEW in 2.0.0] |
| cellDoubleClick | GridCellDoubleClickEvent<T> | Emitted when a cell is double-clicked | [NEW in 2.0.0] |
| rowClick | GridRowClickEvent<T> | Emitted when a row is clicked (includes native event) | [UPD in 2.0.0] |
| rowContext | GridRowContextEvent<T> | Emitted on row context menu | [NEW in 2.0.0] |
| rowDoubleClick | GridRowDoubleClickEvent<T> | Emitted when a row is double-clicked | [NEW in 2.0.0] |
| columnResizeEnd | GridColumnResizeEndEvent<T> | Emitted when a column resize drag ends (key + px width); use to persist widths | [NEW in 3.1.0] |
| sortChange | GridSortEvent<T> | Emitted when single-sort order changes | |
| multiSortChange | GridMultiSortEvent<T> | Emitted when multi-sort order changes | [NEW in 2.2.0] |
| pageChange | GridPageChangeEvent | Emitted for page requests only when [source] is not bound | |
| sourceError | GridSourceRequestErrorEvent<T> | Fallback source-request failure when source.error is not available | [NEW in 3.3.0] |
| selectChange | GridSelectEvent<T> | Emitted when selected row values change | |
| selectedKeysChange | ReadonlyArray<GridSelectionValue<T>> | Controlled-selection proposal emitted after an interaction | [NEW in 3.3.0] |
Notes:
- A cell click also triggers the row click event (bubbling), so listen to one or stop propagation if needed.
- In
sourcemode, sort events are still emitted, but if the source implements sort hooks the grid also updates the source directly. selectChangeremains the compatibility event for all selection changes. In controlled mode, useselectedKeysChangeto update the boundselectedKeysvalue.- With the default
'preserve-unloaded'policy, page changes do not emit selection changes.'loaded-only'may emit an empty or reduced selection when the loaded dataset changes, and changing the configured selection key resets selection.
Public API methods
DataGrid exposes imperative helpers via component instance (e.g. @ViewChild(DataGrid)):
clearSelection()- clears current selection and emitsselectChangeselectAllLoaded()- selects currently loaded rows (multi mode) and emitsselectChangeresetSort()- resets sort state and emits sort eventsresetSourceBuffer()- clears the grid-owned source buffer and invalidates in-flight page resultsrequestPage(page)- requests a page through the source contract or legacypageChangeoutputrequestPageSize(size)- changes size through the shared page request path, starting at page zeroretryPage(page?)- retries the current or supplied source pagesetSort(event)- applies single-sort state and emits sort eventssetMultiSort(items)- applies multi-sort state and emits sort events
GridPagedDataSource reference
The optional source input accepts a page-oriented contract:
| Field / method | Type | Description | |
|---------------------------|--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|
| items | Signal<T[]> | Current page items. |
| loading | Signal<boolean> | Source loading state. |
| error | Signal<unknown \| null> \| undefined | Optional source error state. |
| page | number | Current page index. |
| pageSize | number | Current page size. |
| totalElements | number \| undefined | Optional exact dataset size. Without it, infinity ends at a short page and pagination exposes one optimistic extra page while the current page is full. |
| sort | ReadonlyArray<GridSortItem<T>> \| undefined | Optional source-owned sort state. |
| version | Signal<number> | undefined | Dataset reset marker for local buffer invalidation. |
| immutable | boolean | undefined | Opt in only when the dataset never changes for this source instance; replace the source instance for a new dataset. |
| loadPage(page, options) | (page: number, options?: GridPageRequestOptions) => Promise<GridPageResult<T>> | Preferred stateless loader; result must include the requested page identity and items. |
| updatePage(page) | ((page: number) => Promise<unknown>) \| undefined | Deprecated stateful compatibility fallback. |
| updatePageSize(size) | ((size: number) => Promise<unknown>) \| undefined | Optional page-size handler that resets and loads page zero. |
| updateSort(sort) | ((sort?: GridSortItem<T> \| null) => Promise<unknown>) \| undefined | Optional single-sort handler. |
| updateSorts(sort) | ((sort?: ReadonlyArray<GridSortItem<T>> \| null) => Promise<unknown>) \| undefined | Optional multi-sort handler. |
| prefetchMode | 'sequential' \| 'parallel' \| undefined | Infinity strategy; parallel requires loadPage. |
This contract is intentionally grid-like, not statum-specific, but PagedQueryStore already matches it well enough to be used directly.
Generic stateless source
const source: GridPagedDataSource<User> = {
items,
loading,
page: 0,
pageSize: 25,
totalElements: 0,
async loadPage(page, { signal } = {}) {
const response = await api.users({ page, size: this.pageSize, signal });
return { page, items: response.items, totalElements: response.total };
},
};Use prefetchMode: 'parallel' only with this kind of page-local loadPage result. The grid aborts superseded stateless loads through GridPageRequestOptions.signal and ignores stale results.
Stateful latest-wins source
A PagedQueryStore-like source that mutates shared items, page, and loading in updatePage is supported through the compatibility fallback. Keep prefetchMode: 'sequential'; concurrent calls cannot safely share a single mutable page state. Expose version or call resetSourceBuffer() after external dataset replacement.
GridColumn reference
columns accepts GridColumn<T>[], where GridColumn<T> is a union of three column variants:
- default column (
type+ optionaltypeParams) - value column (
value) - template column (
renderTemplate)
Common (base) fields:
| Field | Type | Description |
|-------------------------|-----------------------------------------------------------------------------|------------------------------------------------------------------------------|
| key | DataKey<T> | Unique column identifier. |
| sortKey | DataKey<T> | Sort key; use gridBackendSortField(...) for an explicit server-only field. |
| sticky | 'left' \| 'right' \| true | Keeps the column fixed; true pins it left. |
| expandBy | DataKey<T> | Data key for expand/collapse. |
| flex | number | Flex-grow factor for width distribution. |
| minWidth / maxWidth | number | Column width limits in pixels. |
| resizable | boolean | Enables header drag resize. |
| cellClass | string \| ((row: T) => string) | Static class or per-row resolver. |
| tooltip | true \| string \| ((row: T) => string) \| TemplateRef<GridTooltipContext> | Tooltip content; true uses the cell value. |
For server-only sort fields that are not properties of the displayed row, use gridBackendSortField('server_field'). It is an explicit, branded string accepted by the current compatibility API. Legacy arbitrary strings remain supported, but a future major release can narrow DataKey<T> to actual row keys.
Renderer-specific fields:
| Variant | Fields | Notes |
|---------------------------|-----------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|
| Built-in / type renderer | type?: GridBuiltInCellType \| string, typeParams?: any, defaultValue?: any | Built-in types are plain/date/number/index/checkbox; custom string types can be handled by local/DI type registries. |
| Value renderer | value: (row: T, ctx) => string \| number | Row identity comes from grid-level rowKey; ctx includes col, index, isPinned. |
| Template renderer | renderTemplate: TemplateRef<...> | Row identity comes from grid-level rowKey. |
Cell text wrapping/clamp behavior:
- Default header text and default body renderers (
plain,date,number,index) are limited to 2 lines with ellipsis. - Template-based renderers (
renderTemplate,reDataGridCell,reDataGridTypeCell, etc.) are not clamped by default and are fully controlled by your template styles. - Cell template contexts also include
isPinnedso pinned top/bottom rows can be rendered differently.
Declarative columns [NEW in 2.0.0]
You can define columns directly in markup via <re-dg-column>, then the grid will normalize them to GridColumn<T> internally.
<re-data-grid [data]="items" [rowKey]="'id'">
<re-dg-column key="name">
<ng-template reHeader>Name</ng-template>
<ng-template reCell let-row="row">{{ row.name }}</ng-template>
</re-dg-column>
<re-dg-column key="age" sortKey="age">
<ng-template reHeader>Age</ng-template>
<ng-template reCell let-row="row">{{ row.age }}</ng-template>
</re-dg-column>
<re-dg-column key="extra" sticky="left" expandBy="age" disabled>
<ng-template reHeader>Extra</ng-template>
<ng-template reCell let-row="row">{{ row.extra }}</ng-template>
</re-dg-column>
</re-data-grid>Notes:
- If at least one
<re-dg-column>is present, declarative columns are used as the base configuration. - When used with column manager state,
visible,disabled, andstickyare taken from state when defined. reHeadermaps toheaderTemplate.reCellreceives value as$implicit, and the row is available aslet-row="row"viaRenderTemplateData.- Most
GridColumn<T>fields are available as<re-dg-column>inputs (sortKey,sticky,expandBy,disabled,width,minWidth,maxWidth,flex,resizable,align,cellClass,type,typeParams,defaultValue,value,tooltip).trackis deprecated; userowKey. stickyaccepts'left' | 'right'; legacytruemaps to'left'for backward compatibility.
Tooltip
tooltip is opt-in per column and shows a popover on cell hover.
columns = [
{ key: 'name', header: 'Name', tooltip: true }, // auto tooltip from cell value
{ key: 'email', header: 'Email', tooltip: (row) => row.email },
{ key: 'status', header: 'Status', tooltip: 'User status' },
];Template tooltip:
<ng-template #tip let-row let-col="col" let-index="index" let-value="value">
<div><b>{{ col.header }}</b> #{{ index + 1 }}</div>
<div>{{ value }}</div>
</ng-template>
<re-dg-column key="email" [tooltip]="tip"></re-dg-column>Tooltip template context (GridTooltipContext):
$implicit/rowcolindexvalue
Header groups [NEW in 2.0.0]
Use headerGroups to render an optional top header row (2-level header layout) above regular column headers.
| Field | Type | Description |
|-------------------|-----------------------------------|------------------------------------------------------------------|
| key | string | Unique id of the header group. |
| from | DataKey<T> | Start column key (inclusive). |
| to | DataKey<T> | End column key (inclusive). If omitted, group covers one column. |
| title | string | Plain text title for the group. |
| titleTemplate | TemplateRef<HeaderTemplateData> | Template-based title for the group. |
| align | 'left' \| 'center' \| 'right' | Optional group title alignment (plain title variant). |
Notes:
- Groups are normalized against visible columns only.
- Overlapping or invalid ranges are ignored during normalization.
- Columns not covered by groups are automatically placed into technical spacer groups to keep width alignment.
Templates
| Directive | Parameters | Description | |
|------------------------|-----------------------------------|---------------------------------------------------|------------------|
| reHeader | - | Declarative header template inside re-dg-column | [NEW in 2.0.0] |
| reCell | let-row | Declarative cell template inside re-dg-column | [NEW in 2.0.0] |
| reDataGridHeader | key: string | Template for specific column header by key | |
| reDataGridCell | key: string | Template for specific column cell by key | [NEW in 1.1.0] |
| reDataGridTypeCell | type: string | Template for cells of a specific column type | |
| reDataGridEmpty | - | Template for the empty state (no data) | |
| reDataGridLoading | - | Template for the loading state | |
| reDataGridSortIcon | order: GridSortOrder | undefined | Template for custom sorting icon | [NEW in 1.1.0] |
| reDataGridExpanderIcon | expanded: boolean | Template for custom expander icon | [NEW in 1.1.0] |
CSS Variables
Layout / Appearance:
--re-data-grid-min-height- component min height (200px)--re-data-grid-height- component height (400px)--re-data-grid-rounded- border radius (var(--radius-md, 6px))--re-data-grid-separator-color- outer border color (var(--border-color))--re-data-grid-separator- outer border (1px solid var(--re-data-grid-separator-color))--re-data-grid-surface- table background (#fff)--re-data-grid-active- active color (#2a90f4)--re-data-grid-body-border- scrollable body border (var(--re-data-grid-separator))--re-data-grid-body-radius- scrollable body radius (var(--re-data-grid-rounded))
Empty / Loading States:
--re-data-grid-empty-color- empty text color (#777)--re-data-grid-empty-surface- empty text background (transparent)--re-data-grid-loading-color- loading indicator color (#444)--re-data-grid-loading-surface- loading spinner (rgba(255,255,255,0.5))--re-data-grid-spinner-size- spinner size (2rem)--re-data-grid-spinner-width- spinner ring thickness (0.25rem)--re-data-grid-spinner-track-color- spinner track color (rgba(0, 0, 0, 0.12))--re-data-grid-skeleton-width- skeleton line width (100%)--re-data-grid-skeleton-height- skeleton line height (100%)--re-data-grid-skeleton-rounded- skeleton border radius (var(--re-data-grid-rounded, 0.75rem))--re-data-grid-skeleton-line- skeleton base color (#e7ebf0)--re-data-grid-skeleton-shine- skeleton highlight color (rgba(255, 255, 255, 0.8))
Tooltip:
--re-data-grid-tooltip-surface- tooltip background (#0f172a)--re-data-grid-tooltip-color- tooltip text color (#f8fafc)--re-data-grid-tooltip-radius- tooltip radius (0.5rem)--re-data-grid-tooltip-padding- tooltip padding (0.4rem 0.6rem)--re-data-grid-tooltip-shadow- tooltip shadow (0 8px 24px rgba(15, 23, 42, 0.25))--re-data-grid-tooltip-z- tooltip z-index (60)
Note: for --re-data-grid-skeleton-height it's usually better to use percentages (for example 60% / 70%) so the line scales naturally with row height.
Scrollbar:
--re-data-grid-scrollbar-size- track size (4px)--re-data-grid-scrollbar-offset- inner offset (2px)--re-data-grid-scrollbar-track-rounded- track radius (0.25rem)--re-data-grid-scrollbar-track-surface- track surface (transparent)--re-data-grid-scrollbar-thumb-size- thumb size (8px)--re-data-grid-scrollbar-thumb-color- thumb color (rgba(0,0,0,0.25))--re-data-grid-scrollbar-thumb-active-color- thumb active color (rgba(0,0,0,0.45))--re-data-grid-scrollbar-thumb-rounded- thumb radius (var(--re-data-grid-scrollbar-track-rounded))
Header:
--re-data-grid-header-rounded- header corner radius (var(--re-data-grid-rounded))--re-data-grid-header-surface- header area background (#fff)--re-data-grid-header-body-gap- gap between header and body (0px)--re-data-grid-header-rows-padding- header rows container padding (0)--re-data-grid-header-rows-border-bottom- header rows container bottom border (0)--re-data-grid-header-rows-surface- header rows container background (transparent)--re-data-grid-header-row-height- main header row height (40px)--re-data-grid-header-row-separator-color- main header separator color (#ccc)--re-data-grid-header-row-separator- main header separator (1px solid var(--re-data-grid-header-row-separator-color))--re-data-grid-header-group-row-height- group header row height (var(--re-data-grid-header-row-height))--re-data-grid-header-group-row-separator-color- group header separator color (var(--re-data-grid-header-row-separator-color))--re-data-grid-header-group-row-separator- group header separator (1px solid var(--re-data-grid-header-group-row-separator-color))--re-data-grid-header-group-row-border-bottom- group row container bottom border (0)
Header Cells:
--re-data-grid-header-cell-font-weight- font weight (600)--re-data-grid-header-cell-font-size- font size (0.8rem)--re-data-grid-header-cell-color- text color (#000)--re-data-grid-header-cell-surface- cell background (#fafafa)--re-data-grid-header-cell-line-height- header text line-height for clamp (1.2)--re-data-grid-header-cell-max-lines- header max text lines (2)--re-data-grid-header-cell-min-height- header cell minimum height (auto)--re-data-grid-header-cell-padding- header cell padding (var(--re-data-grid-cell-paddings))--re-data-grid-header-cell-border-right- header column divider (var(--re-data-grid-column-separator))--re-data-grid-header-cell-border-bottom- header bottom divider (var(--re-data-grid-header-row-separator))--re-data-grid-header-cell-radius- header cell radius (0)--re-data-grid-header-cell-direction- header cell flex direction (row)--re-data-grid-header-cell-gap- header cell content gap (0.75rem)--re-data-grid-header-group-cell-font-weight- group font weight (var(--re-data-grid-header-cell-font-weight))--re-data-grid-header-group-cell-font-size- group font size (var(--re-data-grid-header-cell-font-size))--re-data-grid-header-group-cell-color- group text color (var(--re-data-grid-header-cell-color))--re-data-grid-header-group-cell-surface- group cell background (var(--re-data-grid-header-cell-surface))
Footer:
--re-data-grid-footer-separator-color- separator color (#ccc)--re-data-grid-footer-separator- separator line (1px solid var(--re-data-grid-footer-separator-color))--re-data-grid-footer-surface- footer background (#fff)
Rows:
--re-data-grid-row-separator-color- row separator color (#bbb)--re-data-grid-row-separator- separator line (1px solid var(--re-data-grid-row-separator-color))--re-data-grid-row-odd-surface- odd rows background (var(--re-data-grid-cell-surface))--re-data-grid-row-hover-surface- hover rows background (var(--re-data-grid-cell-surface))--re-data-grid-row-hover-color- hover rows text color (var(--re-data-grid-cell-color))--re-data-grid-row-hover-rounded- hover row corner radius (0px)--re-data-grid-row-min-height- row minimum height (auto)--re-data-grid-last-row-border-bottom- final body-row separator (var(--re-data-grid-row-separator))
Columns:
--re-data-grid-column-separator-color- column divider color (transparent)--re-data-grid-column-separator- column divider (1px solid var(--re-data-grid-column-separator-color))--re-data-grid-column-odd-surface- odd column background (var(--re-data-grid-cell-surface))
Cells:
--re-data-grid-cell-paddings- inner paddings (0.4rem 0.625rem)--re-data-grid-cell-font-weight- font weight (400)--re-data-grid-cell-font-size- font size (0.75rem)--re-data-grid-cell-color- text color (#000)--re-data-grid-cell-surface- cell background (#fff)--re-data-grid-cell-line-height- body text line-height for clamp (1.2)--re-data-grid-cell-max-lines- body max text lines (2)--re-data-grid-cell-min-height- body cell minimum height (auto)--re-data-grid-cell-border-right- body cell column divider (var(--re-data-grid-column-separator))--re-data-grid-cell-border-bottom- body cell row divider (var(--re-data-grid-row-separator))--re-data-grid-cell-radius- body cell radius (0)--re-data-grid-cell-direction- body cell flex direction (row)--re-data-grid-cell-gap- body cell content gap (0)--re-data-grid-cell-template-justify- default projected-template alignment (left)--re-data-grid-cell-template-text-align- default projected-template text alignment (left)
Checkbox:
--re-data-grid-checkbox-size- checkbox size (20px)--re-data-grid-checkbox-stroke- checkbox border width (2px)--re-data-grid-checkbox-border- checkbox border color (var(--border-color, #9aa3af))--re-data-grid-checkbox-tick- checkbox tick color (var(--surface-neutral, #fff))--re-data-grid-checkbox-surface- checkbox background (var(--surface-neutral, #fff))--re-data-grid-checkbox-active-color- checkbox active color (var(--primary-color, #2563eb))
Focus Ring:
--re-data-grid-focus-ring-color- keyboard focus outline color (color-mix(in srgb, var(--primary-color, #2a90f4) 55%, transparent))--re-data-grid-focus-ring-width- keyboard focus outline width (2px)--re-data-grid-focus-ring-offset- keyboard focus outline offset (-2px)
Sticky Cells:
--re-data-grid-sticky-header-cell-surface- sticky header cell background (#fff)--re-data-grid-sticky-cell-surface- sticky body cell background (#fdfdfd)--re-data-grid-sticky-cell-row-odd-surface- sticky odd rows background (#fdfdfd)--re-data-grid-sticky-cell-left-shadow- left shadow (2px 0 2px rgba(0,0,0,0.03))--re-data-grid-sticky-cell-right-shadow- right shadow (-2px 0 2px rgba(0,0,0,0.03))
Pinned Sections:
--re-data-grid-pinned-surface- pinned sections background (#fcfcfc)--re-data-grid-pinned-separator-color- separator color (#eee)--re-data-grid-pinned-separator- separator line (1px solid var(--re-data-grid-pinned-separator-color))
Misc:
--re-data-grid-expander-color- expander indicator color (var(--primary-color, currentColor))--re-data-grid-expanded-color- expanded column color (var(--re-data-grid-cell-color, #000))--re-data-grid-expanded-surface- expanded column background (var(--re-data-grid-cell-surface, #fff))
Paginator [NEW in 1.1.0]
The paginator component is used to display a page selector and total count.
Inputs
| Parameter | Type | Default | Description |
|-----------------|-------------|-------------------|----------------------------------------|
| current | number | 0 | Current page |
| pageSize | number | 0 | Number of items per page |
| totalElements | number | 0 | Total number of elements |
| maxShowPages | number | 7 | Maximum number of page buttons to show |
| showFirstLast | boolean | false | Show "First" and "Last" buttons |
| showPerPage | boolean | false | Show page-size dropdown |
| pageSizeOptions | number[] | [10,20,50,100] | Options for per-page dropdown |
| perPageLabel | string | Items per page: | Label near dropdown |
| firstLabel | string | First | Fallback label for first-page button |
| lastLabel | string | Last | Fallback label for last-page button |
Outputs
| Event | Type | Description |
|-----------------|-----------|-----------------------------------------------------------------------|
| pageChange | number | Emitted when the page changes. Returns the new page index (0-based). |
| pageSizeChange | number | Emitted when per-page value changes. |
Paginator templates
You can customize first/last controls with ng-template:
<re-data-grid-paginator
showFirstLast
showPerPage
[current]="page"
[pageSize]="20"
[pageSizeOptions]="[10, 20, 50]"
[totalElements]="total"
(pageChange)="page = $event"
(pageSizeChange)="pageSize = $event"
>
<ng-template reDataGridPaginatorFirst let-targetPage let-disabled="disabled">
<span [style.opacity]="disabled ? 0.5 : 1"><< First</span>
</ng-template>
<ng-template reDataGridPaginatorPage let-label="label" let-active="active">
<span [style.fontWeight]="active ? 700 : 400">{{ label }}</span>
</ng-template>
<ng-template reDataGridPaginatorLast let-targetPage let-disabled="disabled">
<span [style.opacity]="disabled ? 0.5 : 1">Last >></span>
</ng-template>
</re-data-grid-paginator>Template contexts:
reDataGridPaginatorFirst/reDataGridPaginatorLast:$implicit(target page),current,total,disabledreDataGridPaginatorPage:$implicit/page(0-based index),label(1-based display),current,total,active
CSS Variables
First / Last controls:
--re-data-grid-paginator-edge-min-width- min width (2.5rem)--re-data-grid-paginator-edge-height- control height (var(--re-data-grid-paginator-page-size))--re-data-grid-paginator-edge-paddings- control paddings (0 0.625rem)--re-data-grid-paginator-edge-border- control border (var(--re-data-grid-paginator-page-border))--re-data-grid-paginator-edge-rounded- control border radius (var(--re-data-grid-paginator-page-rounded))--re-data-grid-paginator-edge-surface- control background (var(--re-data-grid-paginator-page-surface))--re-data-grid-paginator-edge-color- control text color (var(--re-data-grid-paginator-page-color))--re-data-grid-paginator-edge-font-size- control font size (var(--re-data-grid-paginator-page-font-size))--re-data-grid-paginator-edge-hover-surface- hover background (var(--re-data-grid-paginator-page-hover-surface))--re-data-grid-paginator-edge-hover-color- hover text color (var(--re-data-grid-paginator-page-hover-color))--re-data-grid-paginator-edge-disabled-opacity- disabled opacity (0.5)
Layout:
--re-data-grid-paginator-gap- gap between paginator elements (0.5rem)
Page:
--re-data-grid-paginator-page-size- base page button size, used as default min-width and height (1.75rem)--re-data-grid-paginator-page-min-width- page button min width (var(--re-data-grid-paginator-page-size))--re-data-grid-paginator-page-height- page button height (var(--re-data-grid-paginator-page-size))--re-data-grid-paginator-page-paddings- page button paddings (0 0.5rem)--re-data-grid-paginator-page-border- page button border (1px solid var(--re-data-grid-paginator-separator-color, #e2e8f0))--re-data-grid-paginator-page-separator-color- page button border color (var(--re-data-grid-separator-color, var(--border-color, #e2e8f0)))--re-data-grid-paginator-page-rounded- page button border radius (var(--re-data-grid-rounded, var(--radius-md, 0.375rem)))--re-data-grid-paginator-page-surface- page button background (var(--re-data-grid-surface, white))--re-data-grid-paginator-page-color- page button text color (var(--text-primary, #1e293b))--re-data-grid-paginator-page-font-size- page button font size (0.875rem)--re-data-grid-paginator-page-width- page button width (auto)--re-data-grid-paginator-page-flex-shrink- page button flex shrink (0)--re-data-grid-paginator-page-font-variant-numeric- numeric glyph variant (normal)
Active Page:
--re-data-grid-paginator-page-active-surface- active page button background (var(--re-data-grid-active, #3b82f6))--re-data-grid-paginator-page-active-color- active page button text color (white)--re-data-grid-paginator-page-active-border-color- active page border color (var(--re-data-grid-paginator-page-active-surface))
Hover Page:
--re-data-grid-paginator-page-hover-surface- page button hover background (var(--re-data-grid-active, #3b82f6))--re-data-grid-paginator-page-hover-color- page button hover text color (white)
Column manager [NEW in 2.0.0]
Column manager is a secondary entrypoint that provides a dropdown UI for reordering, show/hide, and pinning columns.
import { DataGridColumnManager } from '@reforgium/data-grid/column-manager';<re-data-grid-column-manager
triggerLabel="Columns"
[columns]="managedColumns()"
(columnsChange)="managedColumns.set($event)"
/>
<re-data-grid [columns]="managedColumns()" ... />Custom trigger via content projection:
<re-data-grid-column-manager
triggerLabel="Columns"
[columns]="managedColumns()"
(columnsChange)="managedColumns.set($event)"
>
<ng-template reDataGridColumnManagerTrigger>Manage columns</ng-template>
</re-data-grid-column-manager>Custom column title template (controls stay built-in):
<re-data-grid-column-manager [columns]="managedColumns()" (columnsChange)="managedColumns.set($event)">
<ng-template reDataGridColumnManagerColumnTitle let-title let-column="column">
<span class="font-medium">{{ title }}</span>
</ng-template>
</re-data-grid-column-manager>reDataGridColumnManagerColumnTitle context:
$implicit/title- resolved column title (header || key)column- currentGridColumn<T>
Standalone imports for custom trigger/title markers:
import {
DataGridColumnManager,
DataGridColumnManagerColumnTitleDirective,
DataGridColumnManagerTriggerDirective,
} from '@reforgium/data-grid/column-manager';@Component({
imports: [DataGridColumnManager, DataGridColumnManagerTriggerDirective, DataGridColumnManagerColumnTitleDirective],
})
export class ExampleComponent {}Example with both trigger and column title customizations:
<re-data-grid-column-manager [columns]="managedColumns()" (columnsChange)="managedColumns.set($event)">
<ng-template reDataGridColumnManagerTrigger>Manage columns</ng-template>
<ng-template reDataGridColumnManagerColumnTitle let-title>
<span>{{ title }}</span>
</ng-template>
</re-data-grid-column-manager>Inputs:
columns: GridColumn<T>[]triggerLabel?: stringshowAllLabel?: stringhideAllLabel?: stringcontrolsVisible?: boolean- hides search and "show all / hide optional" panelsearchable?: booleanallowReorder?: booleanallowPin?: booleanallowVisibility?: boolean
Outputs:
columnsChange: GridColumn<T>[]
Column manager CSS Variables
--re-data-grid-cm-gap- layout gap (0.5rem)--re-data-grid-cm-rounded- panel/trigger radius (0.625rem)--re-data-grid-cm-border- border (1px solid var(--surface-border, #dfe1e6))--re-data-grid-cm-surface- surface color (var(--surface-neutral, #fff))--re-data-grid-cm-muted- muted text color (var(--text-muted, #64748b))--re-data-grid-cm-active- active color (var(--primary-color, #2a90f4))--re-data-grid-cm-shadow- panel shadow (0 12px 30px rgba(15, 23, 42, 0.18))
Theming via CSS Variables
Variables can be overridden globally (:root) or per grid container.
Quick Theming Examples
Dark Theme Example:
.dark .data-grid {
--re-data-grid-surface: #121416;
--re-data-grid-header-cell-surface: #161a1d;
--re-data-grid-cell-color: #eef2f6;
--re-data-grid-row-separator-color: #262a2e;
--re-data-grid-column-separator-color: #1e2226;
}Compact Mode Example:
.compact-grid {
--re-data-grid-header-row-height: 32px;
--re-data-grid-cell-font-size: 12px;
--re-data-grid-cell-paddings: 2px 8px;
}Custom Template Examples
Cell Template by Type
<ng-template reDataGridTypeCell="link" let-row="row">
<a [href]="value" target="_blank">{{ value }}</a>
</ng-template>Cell Template [NEW in 1.1.0]
<ng-template reDataGridCell="fullName" let-row="row">
<span><b>{{ row.firstName }}</b> {{ row.lastName }}</span>
</ng-template>Custom Header Template:
<ng-template reDataGridHeader="name" let-value="value">
<span class="header-with-icon">
<icon name="user" size="20" />
{{ value }}
</span>
</ng-template>Compatibility
- Angular: 18+
- Rendering: ESM
- Signals: required
- Zone.js: optional
License
MIT
