Angular quick start
import { Component } from "@angular/core";
import { AwbGrid, type NgColDef } from "@agility-workbench/angular-grid";
@Component({
standalone: true,
imports: [AwbGrid],
template: `
<div style="height: 420px">
<awb-grid
rowIdKey="id"
[rowData]="rows"
[columnDefs]="columns"
(gridReady)="api = $event"
/>
</div>
`,
})
export class ProductsComponent {
rows = [{ id: "1", name: "Notebook" }];
columns: NgColDef[] = [{ key: "name", label: "Product" }];
}
AwbGrid is standalone, SSR-safe (browser creation is deferred with
afterNextRender), and compatible with zone-based and zoneless applications.
It exposes signal inputs for core options and Angular outputs for common
events. Angular renderers, editors, tooltips, and ActionFrames may receive
params through an input named params or an awbInit(params) method.
NgColDef<Trade> types the row in column callbacks, and a [columnDefs] binding
of that type makes the template's AwbGrid<Trade> type its row-carrying inputs and
outputs (see Typing your rows).
Angular components in grid slots
A cell renderer with a params input:
@Component({
standalone: true,
template: `<strong>{{ params()?.value }}</strong>`,
})
export class BoldCell {
readonly params = input<CellRendererParams>();
}
const column: NgColDef = {
key: "name",
label: "Name",
cellRenderer: BoldCell,
};
A tooltip with awbInit:
@Component({
standalone: true,
template: `<span>{{ text }}</span>`,
})
export class OwnerTooltip implements ITooltipNgComp {
text = "";
awbInit(params: TooltipComponentParams): void {
this.text = `${params.data.owner} · ${params.data.ownerEmail}`;
}
}
ActionFrame components follow the same pattern with IActionFrameNgComp.
Angular components also work in defaultColDef, groupColumnDef, and
pivotResultColumnDef for every component slot.
Angular header components
headerComponent (the header content: label, sort icon, expander) and
headerCellComponent (the whole header cell except its resize handle) accept
Angular components. Params arrive through awbInit(params) or a params
input; when the header's sort or filter state changes, awbRefresh(params)
runs (return false to have the grid re-create the component), or the
params input is set again. The component is destroyed when the header is
rebuilt or the grid is destroyed.
@Component({
standalone: true,
template: `<button type="button" (click)="params?.progressSort()">{{ label }}</button>`,
})
export class SortableHeader implements IHeaderNgComp {
params: HeaderComponentParams | null = null;
label = "";
awbInit(params: HeaderComponentParams): void {
this.awbRefresh(params);
}
awbRefresh(params: HeaderComponentParams): boolean {
this.params = params;
this.label = `${params.displayName} ${params.sort.direction ?? ""}`;
return true;
}
}
const columns: NgColDef[] = [
{ key: "name", label: "Name", headerComponent: SortableHeader },
{ key: "status", label: "Status", headerCellComponent: SortableHeader },
];
Core header components (a class with init/getGui/refresh/destroy, or
a function returning an element or string) pass through unchanged.
Angular cell editor
@Component({
standalone: true,
template: `<input #box [value]="initial" />`,
})
export class TextEditor implements ICellEditorNgComp {
private readonly box = viewChild.required<ElementRef<HTMLInputElement>>("box");
initial = "";
awbInit(params: ICellEditorParams): void {
this.initial = String(params.value ?? "");
}
getValue(): unknown {
return this.box().nativeElement.value;
}
focus(): void {
this.box().nativeElement.focus();
}
}
Live and creation-only inputs
Most inputs are live: changing one reconfigures the mounted grid in place. A
few are read only when the grid is created, and changing one of those later
has no effect. In development mode (isDevMode()) the grid logs one warning
per input when that happens:
[agility-workbench] <awb-grid> input "rowHeight" changed after the grid was created. It is read only at creation, so the change is ignored. Re-create the grid to apply it (for example, render it under an @if or a keyed @for so Angular destroys and creates a new <awb-grid>).
To apply a creation-only change, re-create the component, for example by
keying it in a single-item @for:
@for (height of [rowHeight]; track height) {
<awb-grid [rowHeight]="height" [rowData]="rows" [columnDefs]="columns" />
}
Objects and functions that are re-created with the same contents do not count as changes.
| Inputs | |
|---|---|
| Live | rowData, columnDefs, loading; rowHover, columnHover, zebraRows, getRowClass, getRowStyle, getRowPresentation, ariaLabel, ariaLabelledBy, highlightActiveCell, cellSelection, rangeSelection, fillHandle, columnSelection, headerKeyboardNavigation, showColumnButtonsOnHover, bodyContextMenu, editTrigger, readOnlyEdit, pinnedRowsEditable, rowPinningMenu, rowInsertionMenu, suppressKeyboardEdit, suppressTypeToEdit, moveAfterEdit, commitOnBlur, asyncTransactionWaitMs, loadingMessage, noRowsMessage; toolbar, quickFilter, tooltip, columnPanel, savedViews, sheets, pagination, paginationControls, rowSelection, isRowSelectable, theme, icons, headerHeight, leafHeaderHeight, parentHeaderHeight, pinnedTopRowData, pinnedBottomRowData, isRowPinned, groupRowsSticky, groupDisplayType, groupSortMode, groupRowsSelectable, groupChildCount, pivotColumnMoveMode, serverSideDataSource, serverSideAggregationSource; pivotMode, pivotColumns; treeData.keyboardNavigationMode, treeData.enableKeyboardNavigationModeSwitch |
| Live (current value is read on each call) | onBeforeCellCommit, fillOperation, getColumnMenuItems, multiColumnMenu (except false at creation, which removes the multi-column menu for the grid's lifetime). Outputs are always live |
| Creation-only | rowHeight, getRowId, rowIdKey, rowDataMode, rowModelType, serverSideBlockSize, overscanRowCount, minResizeWidth, maxColumnWidth, allowExportAsCSV, allowExportAsExcel, rowNumbers, selectAllRowsOnHeaderClick, selectAllScope, selectionPersistence, clearSelectionOnBodyClick, resetPageOn, pageSize, pageSizes, paginationUnknownTotalTooltip, getGroupChildCount, autosizeColumnsOnDataChange, undoLimit, initialSort, showSortPriority, reevaluateOnEdit, groupColumnDef, groupDefaultExpanded, pivotResultColumnDef, maxPivotColumns, treeData (all other members), isFullWidthRow, fullWidthCellRenderer, defaultColDef, pivotNoValuesMessage, pivotEmptyMessage, filterDebounceMs, cellFlashDuration, cellFadeDuration, suppressStyleInjection, styleNonce |
The creation-only list is the core's CREATION_ONLY_OPTION_KEYS, minus the
inputs the binding bridges. The React binding
splits its props the same way.
Outputs and API access
<awb-grid
[rowData]="rows"
[columnDefs]="columns"
(gridReady)="onGridReady($event)"
(cellClicked)="onCellClicked($event)"
(cellDoubleClicked)="onCellDoubleClicked($event)"
(cellValueChanged)="saveChange($event)"
(selectionChanged)="selectionChanged($event)"
(sortChanged)="sortChanged($event)"
(filterChanged)="filterChanged($event)"
(columnsChanged)="columnsChanged($event)"
(paginationChanged)="paginationChanged($event)"
(gridError)="gridError($event)"
/>
(columnsChanged) reports every column change with source: "user" for gestures made
through the grid's UI and "api" for API calls, restores and columnDefs updates — persist
on "user" and restoring a saved layout never echoes back into storage (see
Telling user changes from your own).
The core's error event is the (gridError) output: Angular binds an (error) on the host
as a native DOM listener as well as to the output, so a DOM error event on <awb-grid>
would reach the handler too. It is an extra notification; the grid handles the failure as it
would without it.
Every core event has an output named like the event — (rowGroupOpened),
(firstDataRendered), (rowDataUpdated), (bodyScroll), (focusChanged) and the rest of
the callbacks table — with (gridError) the one
rename. Outputs re-enter Angular's zone even though the grid core and its high-frequency
listeners run outside it; the outputs added for the remaining events (scroll ticks, repaints,
focus moves among them) do so only while something is bound to them, so an unused
(bodyScroll) costs no change detection. api.on(eventName, handler) still works for any
event.
The template-reference form gives markup-level API access:
<button (click)="grid.api?.exportDataAsCsv()">Export</button>
<awb-grid #grid="awbGrid" [rowData]="rows" [columnDefs]="columns" />
grid.api is null until the browser-side grid is ready.
TemplateRef menu slots
readonly shortcut = viewChild.required<TemplateRef<unknown>>("shortcut");
readonly columnMenuItems = ({ items }: { items: NgMenuItem[] }) => [
...items,
{
id: "open",
label: "Open",
right: this.shortcut(),
onClick: () => this.openSelected(),
},
];
<ng-template #shortcut><kbd>Enter</kbd></ng-template>
<awb-grid [getColumnMenuItems]="columnMenuItems" />
Angular menu items accept TemplateRef values in their left and right
slots for both column and body menus.