# Angular Integration

> Install and configure @toolbox-web/grid-angular — feature inputs, renderers, editors, events, inject functions, and Angular-specific patterns.

The `@toolbox-web/grid-angular` package provides Angular integration for the `<tbw-grid>` data grid component.

:::note[Where to find feature docs]
This page covers Angular-specific setup and APIs (feature inputs, inject functions, Angular directives). Core grid features and plugins — cell renderers, editors, events, master-detail, sorting, filtering, selection, etc. — are documented on the [Core](/grid/core.md) and [Plugins](/grid/plugins.md) pages, each with an Angular tab and runnable demos.
:::

## Compatibility

| Angular version | Support level |
| --------------- | ------------- |
| 21              | **Tested** — used in demos and CI |
| 20              | **Tested** |
| 17 – 19         | Supported (minimum peer dependency) |
| &lt; 17         | Not supported — adapter uses `input()` / `output()` signal APIs |

## Installation

#### npm

  ```bash
  npm install @toolbox-web/grid @toolbox-web/grid-angular
  ```

#### yarn

  ```bash
  yarn add @toolbox-web/grid @toolbox-web/grid-angular
  ```

#### pnpm

  ```bash
  pnpm add @toolbox-web/grid @toolbox-web/grid-angular
  ```

#### bun

  ```bash
  bun add @toolbox-web/grid @toolbox-web/grid-angular
  ```

## Setup

No 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](#three-ways-to-configure-features) below.

```typescript
// 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

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 |

> **`gridConfig` users 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

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:

```typescript
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-line `imports` addition per feature with **zero template changes**.

### 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

The simplest way to use the grid is with the `Grid` directive and per-feature directives:

```typescript
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

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](/grid/angular/api/directives/grid.md).

| Per-Feature Directive (recommended) | Input | Feature Import | Description |
|-------------------------------------|-------|----------------|-------------|
| [`GridSelectionDirective`](/grid/angular/api/directives/gridselectiondirective.md) | `[selection]` | `features/selection` | Cell, row, or range selection |
| [`GridEditingDirective`](/grid/angular/api/directives/grideditingdirective.md) | `[editing]` | `features/editing` | Inline cell editing |
| [`GridMultiSortDirective`](/grid/angular/api/directives/gridmultisortdirective.md) | `[multiSort]` | `features/multi-sort` | Multi-column sorting |
| [`GridFilteringDirective`](/grid/angular/api/directives/gridfilteringdirective.md) | `[filtering]` | `features/filtering` | Column filtering |
| [`GridClipboardDirective`](/grid/angular/api/directives/gridclipboarddirective.md) | `[clipboard]` | `features/clipboard` | Copy/paste support |
| [`GridContextMenuDirective`](/grid/angular/api/directives/gridcontextmenudirective.md) | `[contextMenu]` | `features/context-menu` | Right-click context menu |
| [`GridReorderColumnsDirective`](/grid/angular/api/directives/gridreordercolumnsdirective.md) | `[reorderColumns]` | `features/reorder-columns` | Column drag-to-reorder |
| [`GridVisibilityDirective`](/grid/angular/api/directives/gridvisibilitydirective.md) | `[visibility]` | `features/visibility` | Column visibility panel |
| [`GridPinnedColumnsDirective`](/grid/angular/api/directives/gridpinnedcolumnsdirective.md) | `[pinnedColumns]` | `features/pinned-columns` | Sticky left/right columns |
| [`GridPinnedRowsDirective`](/grid/angular/api/directives/gridpinnedrowsdirective.md) | `[pinnedRows]` | `features/pinned-rows` | Sticky top/bottom rows |
| [`GridGroupingColumnsDirective`](/grid/angular/api/directives/gridgroupingcolumnsdirective.md) | `[groupingColumns]` | `features/grouping-columns` | Multi-level column headers |
| [`GridGroupingRowsDirective`](/grid/angular/api/directives/gridgroupingrowsdirective.md) | `[groupingRows]` | `features/grouping-rows` | Row grouping |
| [`GridColumnVirtualizationDirective`](/grid/angular/api/directives/gridcolumnvirtualizationdirective.md) | `[columnVirtualization]` | `features/column-virtualization` | Virtualize columns for wide grids |
| [`GridRowDragDropDirective`](/grid/angular/api/directives/gridrowdragdropdirective.md) | `[rowDragDrop]` / `[reorderRows]` | `features/row-drag-drop` | Row drag-and-drop (within and across grids) |
| [`GridTreeDirective`](/grid/angular/api/directives/gridtreedirective.md) | `[tree]` | `features/tree` | Hierarchical tree view |
| [`GridMasterDetailDirective`](/grid/angular/api/directives/gridmasterdetaildirective.md) | `[masterDetail]` | `features/master-detail` | Expandable detail rows |
| [`GridResponsiveDirective`](/grid/angular/api/directives/gridresponsivedirective.md) | `[responsive]` | `features/responsive` | Card layout for narrow viewports |
| [`GridTooltipDirective`](/grid/angular/api/directives/gridtooltipdirective.md) | `[tooltip]` | `features/tooltip` | Cell / header tooltips |
| [`GridUndoRedoDirective`](/grid/angular/api/directives/gridundoredodirective.md) | `[undoRedo]` | `features/undo-redo` | Edit undo/redo |
| [`GridExportDirective`](/grid/angular/api/directives/gridexportdirective.md) | `[export]` | `features/export` | CSV/Excel export |
| [`GridPrintDirective`](/grid/angular/api/directives/gridprintdirective.md) | `[print]` | `features/print` | Print support |
| [`GridPivotDirective`](/grid/angular/api/directives/gridpivotdirective.md) | `[pivot]` | `features/pivot` | Pivot table functionality |
| [`GridServerSideDirective`](/grid/angular/api/directives/gridserversidedirective.md) | `[serverSide]` | `features/server-side` | Server-side data loading |
| [`GridStickyRowsDirective`](/grid/angular/api/directives/gridstickyrowsdirective.md) | `[stickyRows]` | `features/sticky-rows` | Pin selected data rows below the header on scroll |

**Core Config Inputs (no feature import needed):**

| Input | Type | Description |
|-------|------|-------------|
| `[rows]` | `T[]` | Row data — omit when using `[serverSide]` |
| `[columns]` | [`ColumnConfig`](/grid/api/core/interfaces/columnconfig.md)`[]` | Column definitions (alternative to `gridConfig.columns`) |
| `[gridConfig]` | [`GridConfig`](/grid/api/core/interfaces/gridconfig.md) | 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

Use [`injectGrid()`](/grid/angular/api/utilities/injectgrid.md) for programmatic access to the grid. It provides a reactive API via signals — see [`InjectGridReturn`](/grid/angular/api/types/injectgridreturn.md) for the full return shape:

```typescript
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

Feature imports export **scoped inject functions** for type-safe programmatic access to plugin functionality — no direct plugin references needed.

```typescript
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

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:

```typescript
// Target a specific grid by CSS selector
gridSelection = injectGridSelection('tbw-grid.primary');
gridExport = injectGridExport('#secondary-grid');
```

| Function | Import | Key Methods |
|----------|--------|-------------|
| [`injectGrid()`](/grid/angular/api/utilities/injectgrid.md) | `@toolbox-web/grid-angular` | `element`, `isReady`, `config`, `forceLayout`, `toggleGroup`, `registerStyles`, `visibleColumns` |
| [`injectGridSelection()`](/grid/angular/api/features/injectgridselection.md) | `features/selection` | `selectAll`, `clearSelection`, `getSelection`, `selectedRowIndices` (Signal) |
| [`injectGridFiltering()`](/grid/angular/api/features/injectgridfiltering.md) | `features/filtering` | `setFilter`, `clearAllFilters`, `getFilters`, `getFilteredRowCount` |
| [`injectGridExport()`](/grid/angular/api/features/injectgridexport.md) | `features/export` | `exportToCsv`, `exportToExcel`, `exportToJson`, `isExporting` |
| [`injectGridPrint()`](/grid/angular/api/features/injectgridprint.md) | `features/print` | `print`, `isPrinting` |
| [`injectGridUndoRedo()`](/grid/angular/api/features/injectgridundoredo.md) | `features/undo-redo` | `undo`, `redo`, `canUndo` (Signal), `canRedo` (Signal) |

### Signal-Based Selection Example

```typescript
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

Register application-wide renderers and editors for specific data types using [`provideGridTypeDefaults()`](/grid/angular/api/utilities/providegridtypedefaults.md):

```typescript
// app.config.ts
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`](/grid/angular/api/utilities/gridtyperegistry.md):

```typescript
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

The grid supports two complementary ways to customize icons — see the [Theming Guide → Icon Customization](/grid/guides/theming.md#icon-customization) for both the CSS and JavaScript approaches:

- **CSS variables** (`--tbw-icon-*`) — preferred for themes and static customization; no JavaScript needed.
- **`gridConfig.icons`** — for dynamic icons, icon libraries, or `HTMLElement` instances; takes precedence over CSS.

[`provideGridIcons()`](/grid/angular/api/utilities/providegridicons.md) is the Angular DI wrapper for the JS path — it injects `icons` into `gridConfig.icons` for every grid in the application:

```typescript
// app.config.ts
import { provideGridIcons } from '@toolbox-web/grid-angular';

export const appConfig = {
  providers: [
    provideGridIcons({
      sortAsc: '<svg>...</svg>',
      sortDesc: '<svg>...</svg>',
      filter: '<svg>...</svg>',
    }),
  ],
};
```

## Manual Plugin Instantiation

While feature inputs are recommended, you can instantiate plugins manually for custom configurations or third-party plugins:

```typescript
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

### Custom Tool Panels

Use the [`GridToolPanel`](/grid/angular/api/directives/gridtoolpanel.md) 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`):

```typescript
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).

### Header & Toolbar Content

Inject Angular components into the grid's shell header (left zone) or toolbar (right zone) via the [`GridHeaderContent`](/grid/angular/api/directives/gridheadercontent.md) and [`GridToolbarContent`](/grid/angular/api/directives/gridtoolbarcontent.md) directives. These wrap the imperative `registerHeaderContent` / `registerToolbarContent` APIs and mount an `<ng-template>` so projected components retain full DI, change detection, and signal reactivity.

```typescript
import { Component, signal } from '@angular/core';
import { Grid, GridHeaderContent, GridToolbarContent } from '@toolbox-web/grid-angular';

@Component({
  imports: [Grid, GridHeaderContent, GridToolbarContent, HeaderNavComponent, ToolbarNavComponent],
  template: `
    <tbw-grid [rows]="rows()" [gridConfig]="config()">
      <tbw-grid-header-content id="calendar-nav" [order]="0">
        <ng-template>
          <app-header-nav [year]="year()" (yearChange)="onYearChange($event)" />
        </ng-template>
      </tbw-grid-header-content>
      <tbw-grid-toolbar-content id="calendar-buttons" [order]="0">
        <ng-template>
          <app-toolbar-nav (previous)="prev()" (today)="today()" (next)="next()" />
        </ng-template>
      </tbw-grid-toolbar-content>
    </tbw-grid>
  `,
})
export class CalendarGridComponent {
  year = signal(new Date().getFullYear());
  onYearChange(year: number) { this.year.set(year); }
}
```

**Inputs:** `id` (optional — auto-generated if omitted), `order` (default `100`, lower = first). Template bindings re-evaluate via Angular change detection without re-registering with the grid.

## 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](/grid/plugins/server-side.md) page (it has runnable demos and an Angular tab in every example). The two Angular-specific points worth calling out here:

1. **Use the per-feature directive (recommended).** Add [`GridServerSideDirective`](/grid/angular/api/directives/gridserversidedirective.md) to your component's `imports` if 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 on `Grid` itself still works in v1.x but will be removed in v2.0.
2. **Return the `HttpClient` `Observable` directly from `getRows`.** No `firstValueFrom`, no manual `takeUntil`. The grid subscribes once and calls `unsubscribe()` when the request is superseded (sort change, fast scrolling, `refresh()`); `HttpClient` cancels the underlying XHR in response, which is the only correct way to avoid stale-buffer races.

```typescript
import { Component, inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { Grid } from '@toolbox-web/grid-angular';
import { GridServerSideDirective } from '@toolbox-web/grid-angular/features/server-side';
import type { GridConfig } from '@toolbox-web/grid';
import type { ServerSideDataSource } from '@toolbox-web/grid/plugins/server-side';

@Component({
  selector: 'app-employee-grid',
  imports: [Grid, GridServerSideDirective],
  template: `<tbw-grid [gridConfig]="gridConfig" style="height: 400px; display: block;"></tbw-grid>`,
})
export class EmployeeGridComponent {
  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 the `HttpClient` call in your own service (e.g. with `shareReplay`), or evaluate [`@tanstack/angular-query-experimental`](https://tanstack.com/query/latest/docs/framework/angular/overview) — note the `experimental` tag.

## Dynamic Row Updates

```typescript
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

### OnPush Change Detection

```typescript
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  // ...
})
export class EmployeeGridComponent { }
```

### Define Columns Once

```typescript
// ✅ Good — assigned once
readonly columns: ColumnConfig[] = [
  { field: 'id', header: 'ID' },
  { field: 'name', header: 'Name' },
];

// ❌ Bad — getter creates new array every check
get columns() {
  return [{ field: 'id', header: 'ID' }, { field: 'name', header: 'Name' }];
}
```

## Troubleshooting

### Feature input not working

Ensure you imported the feature side-effect in your `main.ts`:

```typescript
// main.ts — must be imported before bootstrap
import { GridSelectionDirective } from '@toolbox-web/grid-angular/features/selection';
```

### "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:

```typescript
import { Grid, TbwGridColumn } from '@toolbox-web/grid-angular';

@Component({
  imports: [Grid, TbwGridColumn], // Grid → <tbw-grid>, TbwGridColumn → <tbw-grid-column>
  // ...
})
```

### Grid not rendering

The grid needs a defined height for virtualization. See [Troubleshooting → Height & Virtualization](/grid/guides/troubleshooting.md#height--virtualization) for solutions.

### Row updates not reflected

The grid detects updates via reference equality. Always assign a new array:

```typescript
// ✅ New reference — grid re-renders
this.employees.update(rows => [...rows, newRow]);

// ❌ Same reference — grid won't detect change
this.employees().push(newRow);
```

## See Also

- **[Base Classes](/grid/angular/base-classes.md)** — `BaseOverlayEditor`, `BaseGridEditorCVA`, `BaseFilterPanel`
- **[Reactive Forms](/grid/angular/reactive-forms.md)** — `FormArray` integration with `GridFormArray` and `GridLazyForm`
- **API Reference** — Browse all directives, utilities, and types via the Angular API section in the sidebar
- **[Grid Plugins](/grid/plugins.md)** — All available plugins
- **[Core Features](/grid/core.md)** — Variable row heights, events, column config
