Angular Integration
The @toolbox-web/grid-angular package provides Angular integration for the <tbw-grid> data grid component.
Compatibility
Section titled “Compatibility”| Angular version | Support level |
|---|---|
| 21 | Tested — used in demos and CI |
| 20 | Tested |
| 17 – 19 | Supported (minimum peer dependency) |
| < 17 | Not supported — adapter uses input() / output() signal APIs |
Installation
Section titled “Installation”npm install @toolbox-web/grid @toolbox-web/grid-angular
yarn add @toolbox-web/grid @toolbox-web/grid-angularpnpm add @toolbox-web/grid @toolbox-web/grid-angularbun add @toolbox-web/grid @toolbox-web/grid-angularNo bootstrap step is required. Importing @toolbox-web/grid-angular (or any of its features/* entries) registers the <tbw-grid> custom element as a side effect. Each feature is enabled per-component by importing its directive — see Three Ways to Configure Features below.
// Optional: if you also want to use <tbw-grid> in plain HTML templates// outside an Angular component (rare), add this once in main.ts:import '@toolbox-web/grid';Three Ways to Configure Features
Section titled “Three Ways to Configure Features”You can enable plugins on <tbw-grid> with one of three patterns. Pick whichever fits the component — they can be mixed.
| Pattern | Best for | v2 status |
|---|---|---|
gridConfig.features (object literal) | Configuration-driven apps; the entire grid setup lives in one object | ✅ Unchanged — fully supported |
Per-feature directive (e.g. GridFilteringDirective) | Template-driven apps that want signal inputs/outputs and the smallest bundle | ✅ Recommended — the future-proof path |
Inputs/outputs on Grid (e.g. [filtering], (filterChange)) | v1.x apps already on this style | ⚠️ Deprecated — bindings remain in v1.x for compatibility but will be removed in v2.0 |
gridConfigusers are not affected by the v2 cleanup — only consumers binding feature inputs directly on<tbw-grid>need to switch to per-feature directives before v2.
Recommended: Per-Feature Directives
Section titled “Recommended: Per-Feature Directives”Each feature ships its own attribute-selector directive (GridFilteringDirective, GridSelectionDirective, …) from its secondary entry. Add the directive to the component’s imports and bind exactly the same [input] / (output) you used before:
import { Component } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import { GridFilteringDirective } from '@toolbox-web/grid-angular/features/filtering';import { GridSelectionDirective } from '@toolbox-web/grid-angular/features/selection';
@Component({ imports: [Grid, GridFilteringDirective, GridSelectionDirective], template: ` <tbw-grid [rows]="rows" [filtering]="true" (filterChange)="onFilter($event)" [selection]="'range'" /> `,})export class MyGrid {/* … */}Why this is the preferred path:
- Tree-shakeable typed surface — the feature’s
input()/output()definitions ship inside the feature’s own bundle. Apps that don’t import the directive don’t pay for its compiled metadata in the core bundle. - Compile-time safety — Angular errors with
Can't bind to 'filtering' since it isn't a known property of 'tbw-grid'if you forget the directive (stronger than React/Vue, which silently drop unknown props). - Same bindings, same demos — the directive’s selector matches the existing attributes (
tbw-grid[filtering], tbw-grid[filterChange]), so migrating from the deprecated style is a one-lineimportsaddition per feature with zero template changes.
Legacy: Inputs/Outputs on Grid (deprecated)
Section titled “Legacy: Inputs/Outputs on Grid (deprecated)”The v1.x style — declaring [filtering], (filterChange), etc. directly on the Grid directive without importing a per-feature directive — still works for the entire v1.x line but is @deprecated in JSDoc. v2.0 will remove these bindings from Grid, at which point a one-line directive import per feature is the migration.
Basic Usage
Section titled “Basic Usage”The simplest way to use the grid is with the Grid directive and per-feature directives:
import { Component } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import { GridSelectionDirective } from '@toolbox-web/grid-angular/features/selection';import { GridEditingDirective } from '@toolbox-web/grid-angular/features/editing';import { GridMultiSortDirective } from '@toolbox-web/grid-angular/features/multi-sort';import { GridFilteringDirective } from '@toolbox-web/grid-angular/features/filtering';import type { ColumnConfig } from '@toolbox-web/grid';
interface Employee { id: number; name: string; department: string; salary: number;}
@Component({ selector: 'app-employee-grid', imports: [Grid, GridSelectionDirective, GridEditingDirective, GridMultiSortDirective, GridFilteringDirective], template: ` <tbw-grid [rows]="employees" [columns]="columns" [selection]="'range'" [editing]="'dblclick'" [multiSort]="true" [filtering]="{ debounceMs: 200 }" style="height: 400px; display: block;" /> `,})export class EmployeeGridComponent { employees: Employee[] = [ { id: 1, name: 'Alice', department: 'Engineering', salary: 95000 }, { id: 2, name: 'Bob', department: 'Marketing', salary: 75000 }, { id: 3, name: 'Charlie', department: 'Sales', salary: 85000 }, ];
columns: ColumnConfig<Employee>[] = [ { field: 'id', header: 'ID', type: 'number', width: 70 }, { field: 'name', header: 'Name', editable: true, sortable: true }, { field: 'department', header: 'Department', editable: true, sortable: true }, { field: 'salary', header: 'Salary', type: 'number', format: (v: number) => '$' + v.toLocaleString() }, ];}Feature Input Reference
Section titled “Feature Input Reference”Each feature exposes an input + (optional) output. The recommended way to use them is to import the matching per-feature directive alongside Grid (left column). The same [input] / (output) bindings are also accepted directly on Grid in v1.x for backward compatibility, but those bindings on Grid are @deprecated and will be removed in v2.0 — see the Grid directive API.
| Per-Feature Directive (recommended) | Input | Feature Import | Description |
|---|---|---|---|
GridSelectionDirective | [selection] | features/selection | Cell, row, or range selection |
GridEditingDirective | [editing] | features/editing | Inline cell editing |
GridMultiSortDirective | [multiSort] | features/multi-sort | Multi-column sorting |
GridFilteringDirective | [filtering] | features/filtering | Column filtering |
GridClipboardDirective | [clipboard] | features/clipboard | Copy/paste support |
GridContextMenuDirective | [contextMenu] | features/context-menu | Right-click context menu |
GridReorderColumnsDirective | [reorderColumns] | features/reorder-columns | Column drag-to-reorder |
GridVisibilityDirective | [visibility] | features/visibility | Column visibility panel |
GridPinnedColumnsDirective | [pinnedColumns] | features/pinned-columns | Sticky left/right columns |
GridPinnedRowsDirective | [pinnedRows] | features/pinned-rows | Sticky top/bottom rows |
GridGroupingColumnsDirective | [groupingColumns] | features/grouping-columns | Multi-level column headers |
GridGroupingRowsDirective | [groupingRows] | features/grouping-rows | Row grouping |
GridColumnVirtualizationDirective | [columnVirtualization] | features/column-virtualization | Virtualize columns for wide grids |
GridRowDragDropDirective | [rowDragDrop] / [reorderRows] | features/row-drag-drop | Row drag-and-drop (within and across grids) |
GridTreeDirective | [tree] | features/tree | Hierarchical tree view |
GridMasterDetailDirective | [masterDetail] | features/master-detail | Expandable detail rows |
GridResponsiveDirective | [responsive] | features/responsive | Card layout for narrow viewports |
GridTooltipDirective | [tooltip] | features/tooltip | Cell / header tooltips |
GridUndoRedoDirective | [undoRedo] | features/undo-redo | Edit undo/redo |
GridExportDirective | [export] | features/export | CSV/Excel export |
GridPrintDirective | [print] | features/print | Print support |
GridPivotDirective | [pivot] | features/pivot | Pivot table functionality |
GridServerSideDirective | [serverSide] | features/server-side | Server-side data loading |
Core Config Inputs (no feature import needed):
| Input | Type | Description |
|---|---|---|
[rows] | T[] | Row data — omit when using [serverSide] |
[columns] | ColumnConfig[] | Column definitions (alternative to gridConfig.columns) |
[gridConfig] | GridConfig | Full grid configuration object |
[loading] | boolean | Show the grid’s loading overlay |
[fitMode] | FitMode | Column-fit strategy ('auto', 'fill', 'fill-rest', etc.) |
[customStyles] | string | CSS injected into the grid — useful for custom renderer/editor styling |
[sortable] | boolean | Grid-wide sorting toggle (default: true) |
[filterable] | boolean | Grid-wide filtering toggle (default: true). Requires FilteringPlugin. |
[selectable] | boolean | Grid-wide selection toggle (default: true). Requires SelectionPlugin. |
Programmatic Grid Access
Section titled “Programmatic Grid Access”Use injectGrid() for programmatic access to the grid. It provides a reactive API via signals — see InjectGridReturn for the full return shape:
import { Component, effect } from '@angular/core';import { Grid, injectGrid } from '@toolbox-web/grid-angular';
@Component({ selector: 'app-employee-grid', imports: [Grid], template: ` <tbw-grid [rows]="employees" [columns]="columns"></tbw-grid>
<p>Grid ready: {{ grid.isReady() }}</p> <button (click)="refresh()">Force Layout</button> `,})export class EmployeeGridComponent { grid = injectGrid();
employees = [/* ... */]; columns = [/* ... */];
constructor() { effect(() => { if (this.grid.isReady()) { console.log('Grid element:', this.grid.element()); } }); }
refresh() { this.grid.forceLayout(); }}Feature-Scoped Inject Functions
Section titled “Feature-Scoped Inject Functions”Feature imports export scoped inject functions for type-safe programmatic access to plugin functionality — no direct plugin references needed.
import { GridExportDirective } from '@toolbox-web/grid-angular/features/export';import { injectGridExport } from '@toolbox-web/grid-angular/features/export';import { Component } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';
@Component({ imports: [Grid, GridExportDirective], template: ` <div class="toolbar"> <button (click)="exportCsv()" [disabled]="gridExport.isExporting()">Export CSV</button> </div> <tbw-grid [rows]="employees" [columns]="columns" [export]="true" /> `,})export class ExportGridComponent { employees = [/* ... */]; columns = [/* ... */]; gridExport = injectGridExport();
exportCsv() { this.gridExport.exportToCsv('employees.csv'); }}Available Inject Functions
Section titled “Available Inject Functions”See the API Reference > Inject Functions section for detailed method signatures, return types, and examples.
All inject functions accept an optional selector parameter (defaults to 'tbw-grid') to target a specific grid when a component contains multiple grids:
// Target a specific grid by CSS selectorgridSelection = injectGridSelection('tbw-grid.primary');gridExport = injectGridExport('#secondary-grid');| Function | Import | Key Methods |
|---|---|---|
injectGrid() | @toolbox-web/grid-angular | element, isReady, config, forceLayout, toggleGroup, registerStyles, visibleColumns |
injectGridSelection() | features/selection | selectAll, clearSelection, getSelection, selectedRowIndices (Signal) |
injectGridFiltering() | features/filtering | setFilter, clearAllFilters, getFilters, getFilteredRowCount |
injectGridExport() | features/export | exportToCsv, exportToExcel, exportToJson, isExporting |
injectGridPrint() | features/print | print, isPrinting |
injectGridUndoRedo() | features/undo-redo | undo, redo, canUndo (Signal), canRedo (Signal) |
Signal-Based Selection Example
Section titled “Signal-Based Selection Example”import { Component, computed } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import { injectGridSelection } from '@toolbox-web/grid-angular/features/selection';
@Component({ selector: 'app-employee-grid', imports: [Grid], template: ` <tbw-grid [rows]="employees" [gridConfig]="gridConfig"></tbw-grid> <p>Selected: {{ selectedCount() }}</p> <button (click)="gridSelection.selectAll()">Select All</button> <button (click)="gridSelection.clearSelection()">Clear</button> `,})export class EmployeeGridComponent { gridSelection = injectGridSelection(); selectedCount = computed(() => this.gridSelection.selectedRowIndices().length);
gridConfig = { features: { selection: { mode: 'row', checkbox: true } }, };}Type-Level Defaults
Section titled “Type-Level Defaults”Register application-wide renderers and editors for specific data types using provideGridTypeDefaults():
import { ApplicationConfig } from '@angular/core';import { provideGridTypeDefaults } from '@toolbox-web/grid-angular';import { CurrencyRendererComponent, DateRendererComponent } from './renderers';
export const appConfig: ApplicationConfig = { providers: [ provideGridTypeDefaults({ currency: { renderer: CurrencyRendererComponent }, date: { renderer: DateRendererComponent }, }), ],};For dynamic registration at runtime, inject GridTypeRegistry:
import { inject } from '@angular/core';import { GridTypeRegistry } from '@toolbox-web/grid-angular';
export class AppComponent { private registry = inject(GridTypeRegistry);
ngOnInit() { this.registry.register('currency', { renderer: CurrencyCellComponent, }); }}Custom Icons
Section titled “Custom Icons”The grid supports two complementary ways to customize icons — see the core Icon Customization reference and the Theming Guide for the full picture:
- CSS variables (
--tbw-icon-*) — preferred for themes and static customization; no JavaScript needed. gridConfig.icons— for dynamic icons, icon libraries, orHTMLElementinstances; takes precedence over CSS.
provideGridIcons() is the Angular DI wrapper for the JS path — it injects icons into gridConfig.icons for every grid in the application:
import { provideGridIcons } from '@toolbox-web/grid-angular';
export const appConfig = { providers: [ provideGridIcons({ sortAsc: '<svg>...</svg>', sortDesc: '<svg>...</svg>', filter: '<svg>...</svg>', }), ],};Manual Plugin Instantiation
Section titled “Manual Plugin Instantiation”While feature inputs are recommended, you can instantiate plugins manually for custom configurations or third-party plugins:
import { Component } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import { SelectionPlugin, EditingPlugin, ClipboardPlugin } from '@toolbox-web/grid/all';import type { GridConfig } from '@toolbox-web/grid';
@Component({ selector: 'app-employee-grid', imports: [Grid], template: `<tbw-grid [rows]="employees" [gridConfig]="gridConfig"></tbw-grid>`,})export class EmployeeGridComponent { employees = [/* ... */];
gridConfig: GridConfig = { columns: [ { field: 'id', header: 'ID' }, { field: 'name', header: 'Name', editable: true }, ], plugins: [ new SelectionPlugin({ mode: 'range', checkbox: true }), new EditingPlugin({ editOn: 'dblclick' }), new ClipboardPlugin({ includeHeaders: true }), ], };}Feature inputs and manual plugins can be mixed — the grid merges them, with manual plugins added first.
Angular-Specific Directives
Section titled “Angular-Specific Directives”Custom Tool Panels
Section titled “Custom Tool Panels”Use the GridToolPanel directive to declare a custom sidebar panel inline. Place a <tbw-grid-tool-panel> element inside the grid with an <ng-template> for its content — the template receives the grid element as its implicit context (and as let-grid):
import { Component } from '@angular/core';import { Grid, GridToolPanel } from '@toolbox-web/grid-angular';
@Component({ imports: [Grid, GridToolPanel], template: ` <tbw-grid [rows]="employees" [columns]="columns"> <tbw-grid-tool-panel id="stats" title="Statistics" icon="📊" [order]="10" > <ng-template let-grid> <div class="stats-panel"> <h3>Statistics</h3> <p>Total rows: {{ employees.length }}</p> </div> </ng-template> </tbw-grid-tool-panel> </tbw-grid> `,})export class EmployeeGridComponent { employees = [/* ... */]; columns = [/* ... */];}Inputs: id (required), title (required), icon, tooltip, order (default 100, lower = first).
Server-Side Data Loading
Section titled “Server-Side Data Loading”The full data-source contract, sort/filter modes, prefetching, cancellation and live-update patterns are documented on the Server-Side plugin page (it has runnable demos and an Angular tab in every example). The two Angular-specific points worth calling out here:
- Use the per-feature directive (recommended). Add
GridServerSideDirectiveto your component’simportsif you want to bind via[serverSide]="…"on<tbw-grid>. If you configure server-side through[gridConfig]="{ features: { serverSide: … } }"instead, no extra directive is needed — the deprecated[serverSide]binding onGriditself still works in v1.x but will be removed in v2.0. - Return the
HttpClientObservabledirectly fromgetRows. NofirstValueFrom, no manualtakeUntil. The grid subscribes once and callsunsubscribe()when the request is superseded (sort change, fast scrolling,refresh());HttpClientcancels the underlying XHR in response, which is the only correct way to avoid stale-buffer races.
import { inject } from '@angular/core';import { HttpClient, HttpParams } from '@angular/common/http';import { map } from 'rxjs/operators';import type { GridConfig } from '@toolbox-web/grid';import type { ServerSideDataSource } from '@toolbox-web/grid/plugins/server-side';import { GridServerSideDirective } from '@toolbox-web/grid-angular/features/server-side';private http = inject(HttpClient);
private dataSource: ServerSideDataSource<Employee> = { getRows: (params) => { const httpParams = new HttpParams() .set('offset', params.startNode) .set('limit', params.endNode - params.startNode); return this.http .get<{ items: Employee[]; total: number }>('/api/employees', { params: httpParams }) .pipe(map((data) => ({ rows: data.items, totalNodeCount: data.total }))); },};
gridConfig: GridConfig<Employee> = { columns: [/* … */], features: { serverSide: { dataSource: this.dataSource } },};Caching: Angular has no built-in equivalent to TanStack Query’s request cache. If you need cross-component caching, deduplication or
staleTime, wrap theHttpClientcall in your own service (e.g. withshareReplay), or evaluate@tanstack/angular-query-experimental— note theexperimentaltag.
Dynamic Row Updates
Section titled “Dynamic Row Updates”import { Component, signal } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';
@Component({ imports: [Grid], template: ` <tbw-grid [rows]="employees()" [columns]="columns"></tbw-grid> <button (click)="addEmployee()">Add</button> <button (click)="removeFirst()">Remove First</button> `,})export class EmployeeGridComponent { employees = signal([ { id: 1, name: 'Alice', department: 'Engineering' }, { id: 2, name: 'Bob', department: 'Design' }, ]);
columns = [ { field: 'id', header: 'ID' }, { field: 'name', header: 'Name' }, { field: 'department', header: 'Department' }, ];
private nextId = 3;
addEmployee() { this.employees.update(rows => [ ...rows, { id: this.nextId++, name: 'New Employee', department: 'TBD' }, ]); }
removeFirst() { this.employees.update(rows => rows.slice(1)); }}Tip: Use Angular signals for rows. The grid detects the new array reference and re-renders efficiently.
Performance Tips
Section titled “Performance Tips”OnPush Change Detection
Section titled “OnPush Change Detection”@Component({ changeDetection: ChangeDetectionStrategy.OnPush, // ...})export class EmployeeGridComponent { }Define Columns Once
Section titled “Define Columns Once”// ✅ Good — assigned oncereadonly columns: ColumnConfig[] = [ { field: 'id', header: 'ID' }, { field: 'name', header: 'Name' },];
// ❌ Bad — getter creates new array every checkget columns() { return [{ field: 'id', header: 'ID' }, { field: 'name', header: 'Name' }];}Troubleshooting
Section titled “Troubleshooting”Feature input not working
Section titled “Feature input not working”Ensure you imported the feature side-effect in your main.ts:
// main.ts — must be imported before bootstrapimport { GridSelectionDirective } from '@toolbox-web/grid-angular/features/selection';“is not a known element” errors
Section titled ““is not a known element” errors”The @toolbox-web/grid-angular adapter provides directives that register all <tbw-*> elements with Angular’s template compiler — no CUSTOM_ELEMENTS_SCHEMA needed. Import the directive that matches the element used in your template:
import { Grid, TbwGridColumn } from '@toolbox-web/grid-angular';
@Component({ imports: [Grid, TbwGridColumn], // Grid → <tbw-grid>, TbwGridColumn → <tbw-grid-column> // ...})Grid not rendering
Section titled “Grid not rendering”The grid needs a defined height for virtualization. See Troubleshooting → Height & Virtualization for solutions.
Row updates not reflected
Section titled “Row updates not reflected”The grid detects updates via reference equality. Always assign a new array:
// ✅ New reference — grid re-rendersthis.employees.update(rows => [...rows, newRow]);
// ❌ Same reference — grid won't detect changethis.employees().push(newRow);See Also
Section titled “See Also”- Base Classes —
BaseOverlayEditor,BaseGridEditorCVA,BaseFilterPanel - Reactive Forms —
FormArrayintegration withGridFormArrayandGridLazyForm - API Reference — Complete API documentation for all directives and types
- Grid Plugins — All available plugins
- Core Features — Variable row heights, events, column config