Skip to main content

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
LiverowData, 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-onlyrowHeight, 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.