Angular Integration
The @toolbox-web/grid-angular package provides Angular integration for the <tbw-grid> data grid component.
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-angularRegister the grid web component and enable the features you need:
// main.ts - Import grid and enable featuresimport '@toolbox-web/grid';
// Enable features via side-effect imports// Only import the features you use - this keeps your bundle small!import '@toolbox-web/grid-angular/features/selection';import '@toolbox-web/grid-angular/features/editing';import '@toolbox-web/grid-angular/features/multi-sort';import '@toolbox-web/grid-angular/features/filtering';Basic Usage
Section titled “Basic Usage”The simplest way to use the grid is with the Grid directive and feature inputs:
// Enable features you useimport '@toolbox-web/grid-angular/features/selection';import '@toolbox-web/grid-angular/features/editing';import '@toolbox-web/grid-angular/features/multi-sort';import '@toolbox-web/grid-angular/features/filtering';
import { Component } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import type { ColumnConfig } from '@toolbox-web/grid';
interface Employee { id: number; name: string; department: string; salary: number;}
@Component({ selector: 'app-employee-grid', imports: [Grid], 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 input enables a specific plugin with simplified configuration. See the Grid directive API for the full input/output reference.
| Feature Input | Feature Import | Description |
|---|---|---|
[selection] | features/selection | Cell, row, or range selection |
[editing] | features/editing | Inline cell editing |
[multiSort] | features/multi-sort | Multi-column sorting |
[filtering] | features/filtering | Column filtering |
[clipboard] | features/clipboard | Copy/paste support |
[contextMenu] | features/context-menu | Right-click context menu |
[reorderColumns] | features/reorder-columns | Column drag-to-reorder |
[visibility] | features/visibility | Column visibility panel |
[pinnedColumns] | features/pinned-columns | Sticky left/right columns |
[pinnedRows] | features/pinned-rows | Sticky top/bottom rows |
[groupingColumns] | features/grouping-columns | Multi-level column headers |
[groupingRows] | features/grouping-rows | Row grouping |
[columnVirtualization] | features/column-virtualization | Virtualize columns for wide grids |
[reorderRows] | features/reorder-rows | Row drag-to-reorder |
[tree] | features/tree | Hierarchical tree view |
[masterDetail] | features/master-detail | Expandable detail rows |
[responsive] | features/responsive | Card layout for narrow viewports |
[undoRedo] | features/undo-redo | Edit undo/redo |
[export] | features/export | CSV/Excel export |
[print] | features/print | Print support |
[pivot] | features/pivot | Pivot table functionality |
[serverSide] | features/server-side | Server-side data loading |
Core Config Inputs (no feature import needed):
| Input | Type | Description |
|---|---|---|
[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. |
Custom Cell Renderers
Section titled “Custom Cell Renderers”Use the *tbwRenderer structural directive to customize how cell values are displayed. The directive context exposes $implicit (the cell value), row, and column.
import { Component, input } from '@angular/core';import { Grid, TbwGridColumn, TbwRenderer } from '@toolbox-web/grid-angular';
// Standalone status badge component@Component({ selector: 'app-status-badge', standalone: true, template: ` <span [class]="'badge badge--' + status()"> {{ status() }} </span> `, styles: ` .badge { padding: 4px 8px; border-radius: 4px; } .badge--active { background: #22c55e; color: white; } .badge--inactive { background: #94a3b8; color: white; } .badge--pending { background: #f59e0b; color: black; } `})export class StatusBadgeComponent { status = input.required<string>();}
@Component({ imports: [Grid, TbwGridColumn, TbwRenderer, StatusBadgeComponent], template: ` <tbw-grid [rows]="employees" [gridConfig]="config" style="height: 400px; display: block;"> <!-- Custom renderer for the status column --> <tbw-grid-column field="status"> <app-status-badge *tbwRenderer="let value" [status]="value" /> </tbw-grid-column> </tbw-grid> `,})export class EmployeeGridComponent { employees = [ { id: 1, name: 'Alice', status: 'active' }, { id: 2, name: 'Bob', status: 'inactive' }, { id: 3, name: 'Charlie', status: 'pending' }, ];
config = { columns: [ { field: 'id', header: 'ID', width: 70 }, { field: 'name', header: 'Name' }, { field: 'status', header: 'Status' }, ], };}Custom Cell Editors
Section titled “Custom Cell Editors”Use the *tbwEditor structural directive for inline cell editing. Editor components emit commit and cancel events — the adapter handles wiring automatically.
import { Component, input, output } from '@angular/core';import { Grid, TbwGridColumn, TbwRenderer, TbwEditor } from '@toolbox-web/grid-angular';import '@toolbox-web/grid-angular/features/editing';
// Editor component - emits 'commit' with new value@Component({ selector: 'app-status-editor', standalone: true, template: ` <select [value]="value()" (change)="commit.emit($any($event.target).value)"> <option value="active">Active</option> <option value="inactive">Inactive</option> <option value="pending">Pending</option> </select> `,})export class StatusEditorComponent { value = input<string>(); commit = output<string>(); // Auto-wired: emitting commits the edit cancel = output<void>(); // Auto-wired: emitting cancels the edit}
@Component({ imports: [Grid, TbwGridColumn, TbwRenderer, TbwEditor, StatusBadgeComponent, StatusEditorComponent], template: ` <tbw-grid [rows]="employees" [gridConfig]="config" style="height: 400px; display: block;"> <tbw-grid-column field="status" editable> <app-status-badge *tbwRenderer="let value" [status]="value" /> <app-status-editor *tbwEditor="let value" [value]="value" /> </tbw-grid-column> </tbw-grid> `,})export class EmployeeGridComponent { employees = [/* ... */];
config = { columns: [ { field: 'id', header: 'ID', width: 70 }, { field: 'name', header: 'Name', editable: true }, { field: 'status', header: 'Status', editable: true }, ], features: { editing: true }, };}For complex editors (date pickers, overlays, form-integrated editors), see Base Classes.
Handling Events
Section titled “Handling Events”Listen to grid events using standard Angular event binding:
@Component({ imports: [Grid], template: ` <tbw-grid [rows]="employees" [gridConfig]="config" (cell-click)="onCellClick($event)" (row-click)="onRowClick($event)" (cell-commit)="onCellCommit($event)" (sort-change)="onSortChange($event)" /> `,})export class EmployeeGridComponent { onCellClick(event: CustomEvent) { const { row, column, value } = event.detail; console.log('Cell clicked:', { row, column, value }); }
onRowClick(event: CustomEvent) { const { row, rowIndex } = event.detail; console.log('Row clicked:', row); }
onCellCommit(event: CustomEvent) { const { row, column, newValue, oldValue } = event.detail; console.log('Cell edited:', { row, column, newValue, oldValue }); }
onSortChange(event: CustomEvent) { const { sortState } = event.detail; console.log('Sort changed:', sortState); }}Programmatic Grid Access
Section titled “Programmatic Grid Access”Using viewChild
Section titled “Using viewChild”Access the underlying <tbw-grid> element via Angular’s viewChild:
import { Component, viewChild, ElementRef, effect } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import type { DataGridElement } from '@toolbox-web/grid';
@Component({ selector: 'app-employee-grid', imports: [Grid], template: ` <tbw-grid #grid [rows]="employees" [columns]="columns"></tbw-grid> `,})export class EmployeeGridComponent { private gridRef = viewChild<ElementRef<DataGridElement>>('grid');
constructor() { effect(() => { const grid = this.gridRef()?.nativeElement; if (!grid) return;
grid.ready().then(() => { console.log('Row count:', grid.sourceRows.length);
const selection = grid.getPluginByName('selection'); selection?.selectRow(0); }); }); }}Using injectGrid
Section titled “Using injectGrid”For a more Angular-idiomatic approach, use injectGrid() which provides a reactive API via signals:
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 '@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], 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”| Function | Import | Key Methods |
|---|---|---|
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”Override built-in icons globally using provideGridIcons():
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”Master-Detail Panels
Section titled “Master-Detail Panels”Use GridDetailView for expandable detail panels with Angular templates:
import { Component } from '@angular/core';import { Grid, GridDetailView } from '@toolbox-web/grid-angular';import '@toolbox-web/grid-angular/features/master-detail';import type { GridConfig } from '@toolbox-web/grid';
@Component({ imports: [Grid, GridDetailView], template: ` <tbw-grid [rows]="employees" [gridConfig]="gridConfig"> <ng-template tbwDetailView let-row let-toggle="toggle"> <div class="detail-panel"> <h4>{{ row.name }}</h4> <p>Email: {{ row.email }}</p> <button (click)="toggle()">Close</button> </div> </ng-template> </tbw-grid> `,})export class EmployeeGridComponent { employees = [/* ... */]; gridConfig: GridConfig = { columns: [/* ... */], features: { masterDetail: true }, };}Responsive Card Layout
Section titled “Responsive Card Layout”Use GridResponsiveCard for mobile-friendly card layouts:
import { Component } from '@angular/core';import { Grid, GridResponsiveCard } from '@toolbox-web/grid-angular';import '@toolbox-web/grid-angular/features/responsive';import type { GridConfig } from '@toolbox-web/grid';
@Component({ imports: [Grid, GridResponsiveCard], template: ` <tbw-grid [rows]="employees" [gridConfig]="gridConfig"> <ng-template tbwResponsiveCard let-row let-columns="columns"> <div class="card"> <h3>{{ row.name }}</h3> <p>{{ row.department }} · {{ row.email }}</p> </div> </ng-template> </tbw-grid> `,})export class EmployeeGridComponent { employees = [/* ... */]; gridConfig: GridConfig = { columns: [/* ... */], features: { responsive: { breakpoint: 600, cardTitleField: 'name' } }, };}Custom Tool Panels
Section titled “Custom Tool Panels”Use GridToolPanel for custom sidebar panels:
import { Component } from '@angular/core';import { Grid, GridToolPanel } from '@toolbox-web/grid-angular';import type { GridConfig } from '@toolbox-web/grid';
@Component({ imports: [Grid, GridToolPanel], template: ` <tbw-grid [rows]="employees" [gridConfig]="gridConfig"> <ng-template tbwToolPanel="stats" let-rows> <div class="stats-panel"> <h3>Statistics</h3> <p>Total rows: {{ rows.length }}</p> </div> </ng-template> </tbw-grid> `,})export class EmployeeGridComponent { employees = [/* ... */]; gridConfig: GridConfig = { columns: [/* ... */], shell: { header: { title: 'Employees' }, toolPanels: [{ id: 'stats', label: 'Stats', icon: '📊' }], }, };}Server-Side Data Loading
Section titled “Server-Side Data Loading”Angular-specific pattern using inject(HttpClient) and signals:
import { Component, inject, signal } from '@angular/core';import { HttpClient } from '@angular/common/http';import { Grid } from '@toolbox-web/grid-angular';import '@toolbox-web/grid-angular/features/server-side';import type { GridConfig, ServerDataSource } from '@toolbox-web/grid';
@Component({ imports: [Grid], template: `<tbw-grid [rows]="employees()" [gridConfig]="gridConfig"></tbw-grid>`,})export class EmployeeGridComponent { private http = inject(HttpClient); employees = signal<Employee[]>([]);
private dataSource: ServerDataSource<Employee> = { fetchRows: async (params) => { const queryParams = new URLSearchParams(); if (params.sortModel?.length) { queryParams.set('sort', params.sortModel[0].field); queryParams.set('order', params.sortModel[0].direction); } if (params.filterModel) { queryParams.set('filters', JSON.stringify(params.filterModel)); } queryParams.set('offset', String(params.startRow)); queryParams.set('limit', String(params.endRow - params.startRow));
const response = await fetch(`/api/employees?${queryParams}`); const data = await response.json(); return { rows: data.items, totalRows: data.total }; }, };
gridConfig: GridConfig<Employee> = { columns: [ { field: 'id', header: 'ID', sortable: true }, { field: 'name', header: 'Name', sortable: true }, { field: 'department', header: 'Department' }, ], features: { serverSide: { dataSource: this.dataSource } }, };}See the Server-Side plugin for the full ServerDataSource interface.
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 '@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