Columns
Column definitions describe identity, value access, formatting, interaction, and presentation. The same definition model is used by the core and both framework bindings.
- React
- Angular
- Core TypeScript
const columns: ReactColDef[] = [
{ key: "orderNo", label: "Order", pinned: "left" },
{ key: "customer", label: "Customer", width: 180 },
{ key: "revenue", label: "Revenue", type: ColumnType.CURRENCY },
];
<Grid
rowData={rows}
columnDefs={columns}
rowNumbers
columnPanel={{ trigger: "toolbar" }}
toolbar={{ quickFilter: true }}
/>
columns: NgColDef[] = [
{ key: "orderNo", label: "Order", pinned: "left" },
{ key: "customer", label: "Customer", width: 180 },
{ key: "revenue", label: "Revenue", type: ColumnType.CURRENCY },
];
// template
<awb-grid
[rowData]="rows"
[columnDefs]="columns"
[rowNumbers]="true"
[columnPanel]="{ trigger: 'toolbar' }"
[toolbar]="{ quickFilter: true }"
/>
const core = new GridCore(new CanvasMeasurer(), {
columnDefs: [
{ key: "orderNo", label: "Order", pinned: "left" },
{ key: "customer", label: "Customer", width: 180 },
{ key: "revenue", label: "Revenue", type: ColumnType.CURRENCY },
],
rowNumbers: true,
columnPanel: { trigger: "toolbar" },
toolbar: { quickFilter: true },
});
Essential fields
| Field | Purpose |
|---|---|
key | Reads a field from row data and provides a stable fallback identity |
colId | Explicit public identity for state and API calls |
label | Visible header label |
type | Selects default comparison, formatting, filtering, and editing behavior |
valueGetter | Computes a value instead of reading key; receives the row node (node.data is the row) |
valueFormatter / valueParser | Converts values for display and committed edits |
Users can resize and reorder enabled columns directly. Initial pinning and
visibility use pinned and hidden; the API and column panel can change both
at runtime. Capture the resulting layout with api.getColumnState() and restore
it with api.applyColumnState(state).
Put common capabilities such as sortable, resizable, and filter in
defaultColDef. An explicit column value always wins.
Column types and formatting
import { ColumnType, type ColDef } from "@agility-workbench/grid";
const columnDefs: ColDef[] = [
{ key: "name", label: "Name", type: ColumnType.STRING },
{ key: "quantity", label: "Quantity", type: ColumnType.NUMBER },
{ key: "active", label: "Active", type: ColumnType.BOOLEAN },
{ key: "createdAt", label: "Created", type: ColumnType.DATE },
{ key: "revenue", label: "Revenue", type: ColumnType.CURRENCY },
];
Value getter, formatter, and parser
const column = {
colId: "total",
label: "Total",
valueGetter: (node) => node.data.quantity * node.data.unitPrice,
valueFormatter: ({ value }) => `$${Number(value).toFixed(2)}`,
editable: true,
valueParser: ({ value }) => Number(value),
} satisfies ColDef;
Pinning, visibility, and capabilities
const columnDefs: ColDef[] = [
{ key: "id", label: "ID", pinned: "left", movable: false },
{ key: "internal", label: "Internal", hidden: true },
{ key: "name", label: "Name", hideable: false },
{ key: "actions", label: "Actions", pinned: "right", resizable: false },
];
Users pin and hide columns through the column menu or the
column panel. The API does the same through typed
methods, each emitting the columnsChanged the gesture would (with
source: "api"):
api.setColumnPinned("id", "left"); // or ["id", "name"]; null unpins
api.setColumnsVisible(["internal", "notes"], false);
api.setColumnVisible("internal", true);
// Indexes are positions in api.getColumnState() order: every non-internal leaf,
// hidden ones included, left section, then centre, then right.
api.moveColumn("name", 0);
api.moveColumns(["price", "qty"], 3); // a block, in the order given
Hiding skips hideable: false columns (showing one always works); pinning skips
columns that cannot be pinned. A move keeps each column in its own section —
the index is clamped to that section's range, so pin first to move a column
across sections — and skips movable: false and lockPosition columns. A
column inside a group leaves the group when the target is outside it, as a
header drag does; pass the group's colId to move the whole group.
Locking a column's position
const columnDefs: ColDef[] = [
{ key: "id", label: "ID", lockPosition: "left" }, // or true
{ key: "name", label: "Name" },
{ key: "amount", label: "Amount" },
{ key: "actions", label: "Actions", lockPosition: "right" },
];
lockPosition holds a column at an edge of its section: true or "left" at
the left (after the selection-checkbox column), "right" at the right. A locked
column cannot be dragged, moved from the column panel or moved with
api.moveColumn, and a column dropped next to it lands beside it rather than
displacing it. applyColumnState, saved views and setColumnDefs put locked
columns back at their edge, whatever order they carry. Pinning still applies: a
locked column pinned left sits at the edge of the left section. Inside a column
group the lock holds the column at its group's edge; set lockPosition on the
group definition to lock the whole group.
Explicit width and automatic sizing
const options = {
minResizeWidth: 60,
maxColumnWidth: 360,
columnDefs: [
{ colId: "name", key: "name", label: "Name", minWidth: 120, maxWidth: 280 },
{ key: "notes", label: "Notes", width: 240 },
],
} satisfies GridOptions;
// Fit columns to their rendered content (one columnWidthsChanged for the batch).
api.autoSizeColumns(["name", "notes"]);
api.autoSizeAllColumns();
// Set widths in pixels, as a resize-handle drag would.
api.setColumnWidth("name", 180);
api.setColumnWidths([
{ colId: "name", widthPx: 180 },
{ colId: "notes", widthPx: 320 },
]);
// Stretch or shrink the unpinned columns to fill the centre viewport.
api.sizeColumnsToFit();
A declared width is the column's width: the grid does not resize it to fit
the data, only clamps it to the column's own minWidth / maxWidth. A column
without a width is sized to its header and content on the first data load,
floored at minResizeWidth and capped at maxColumnWidth (a column's own
minWidth / maxWidth take precedence over those two). Once a user drags a
column edge, that width wins until an explicit fit-to-content: double-clicking
the resize handle or calling autoSizeColumns / autoSizeAllColumns measures
content even for a column with a declared width. Widths set with
setColumnWidth(s) behave like a drag: clamped to the column's bounds and kept
across later autosizes.
sizeColumnsToFit() fits the centre section only: the visible unpinned
columns are scaled together, keeping their relative widths, so they exactly
fill the centre viewport. Pinned sections keep their widths (they are
width-clamped and never scroll), as do resizable: false columns and the
row-number and checkbox columns. Each fitted column stays within its
minWidth / maxWidth (falling back to minResizeWidth / maxColumnWidth),
so when those bounds cannot reach the viewport the fit falls short or
overflows. The new widths count as user widths and arrive as one
columnWidthsChanged. Call it after the grid is laid out — before that it does
nothing.
Set autosizeColumnsOnDataChange: true (the default for the server-side row
model) to recompute content widths after each data refresh; declared and
user-resized widths are kept. Enable a leading row-number column with
rowNumbers: true.
Cell spanning
const columnDefs: ColDef[] = [
{
key: "title",
label: "Title",
colSpan: ({ data }) => data.kind === "section" ? 3 : 1,
},
{ key: "owner", label: "Owner" },
{ key: "status", label: "Status" },
];
A span stops at a pinned-section boundary.
Conditional cell styling
const column = {
key: "change",
label: "Change",
cellClass: ({ value }) => Number(value) < 0 ? "is-negative" : "is-positive",
cellStyle: ({ value }) => ({ fontWeight: Number(value) > 10 ? "700" : "400" }),
} satisfies ColDef;
Header content and whole-cell components
const labelOnly = ({ displayName }) => {
const strong = document.createElement("strong");
strong.textContent = displayName;
return strong;
};
const columnDefs: ColDef[] = [
{ key: "name", label: "Name", headerComponent: labelOnly },
{ key: "status", label: "Status", headerCellComponent: labelOnly },
];
headerComponent replaces header content while preserving grid controls.
headerCellComponent replaces the whole header cell except its resize handle.
The framework bindings accept React/Angular components in both slots.
Wrapping header text and auto header height
const options = {
defaultColDef: { wrapHeaderText: true, autoHeaderHeight: true },
columnDefs: [
{
label: "Commercial performance this fiscal year",
children: [
{ key: "units", label: "Units shipped to date across all warehouses", width: 120 },
{ key: "revenue", label: "Recognised revenue in reporting currency", width: 120 },
],
},
],
} satisfies GridOptions;
wrapHeaderText lets a long label break onto several lines instead of being cut
off with an ellipsis; the sort icon, menu button and group expander keep their
place beside it. autoHeaderHeight grows the header level that label sits on
until the whole label fits: the level becomes as tall as its tallest such label
(plus the cell's padding), and never shorter than the configured headerHeight
/ leafHeaderHeight / parentHeaderHeight. Leaf and group levels grow
independently (put the flags on a group definition, or in defaultColDef, for
the group level), and the pinned sections always grow together. The header
re-measures when column widths settle (a resize, autosize or fit), when columns
are shown, hidden, moved or pinned, when definitions or labels change, when the
header heights or the theme change, and when a web font finishes loading.
Without autoHeaderHeight, a wrapped label is clipped at the configured header
height — set a taller headerHeight yourself, or turn auto height on.
Column state
const savedState = api.getColumnState();
// Later: restore widths, order, pinning, and visibility.
api.applyColumnState(savedState);
// Exact restore: hide columns absent from the saved state.
api.applyColumnState(savedState, { defaultState: { hidden: true } });
Column state carries each column's sort too (sort, null when unsorted, and
sortIndex); see Sorting.
Add or replace definitions at runtime
api.setColumnDefs([...baseColumns, { key: "margin", label: "Margin" }]);
api.addSparklineColumn({
colIds: ["jan", "feb", "mar"],
sparklineType: "line",
targetColId: "revenue",
});
api.getColumnDefs() returns the definitions as the grid holds them now, with the
current widths, pinning, visibility, sort and aggregates written in, so
api.setColumnDefs(api.getColumnDefs()) changes nothing. Edit the result and set it
back to add or change one column without rebuilding the list by hand. For a single
column's live object, api.getColumn(colId); for every leaf in display order,
api.getColumns().