Angular
@dzgrid/angular wraps the DOM grid in a standalone <dz-grid> component with signal inputs, for Angular 19 to 22,
zoneless first (zone.js works too). Where the same thing is done without Angular, the tabs show it in
TypeScript and with the Web Component.
Install
Section titled “Install”npm install @dzgrid/core @dzgrid/dom @dzgrid/theme @dzgrid/angularIn angular.json, load the theme before your own global styles:
"styles": ["node_modules/@dzgrid/theme/dzgrid.css", "src/styles.css"]The <dz-grid> host is display: contents (it has no box of its own): size its parent, in global CSS — a
component’s emulated styles do not reach the grid’s DOM.
.grid-box { block-size: 480px; display: flex; flex-direction: column; }.grid-box > dz-grid > .dzg { flex: 1; }A first grid
Section titled “A first grid”import { createGrid } from '@dzgrid/dom';import type { ColumnOrGroup } from '@dzgrid/core';import { EN } from '@dzgrid/dom/locales/en';
type Comet = { id: string; name: string; seen: string; period: number };
const columns: ColumnOrGroup<Comet>[] = [ { id: 'name', header: 'Comet', flex: 2 }, { id: 'seen', header: 'Last seen', type: 'day', width: 140 }, { id: 'period', header: 'Period (years)', type: 'number', numberFormat: { maximumFractionDigits: 1 } },];
createGrid<Comet>(document.querySelector<HTMLElement>('.grid-box')!, { rows: [ { id: 'c1', name: 'Vantor-Elm', seen: '2024-08-19', period: 71.4 }, { id: 'c2', name: 'Ossian Blue', seen: '2025-01-03', period: 6.2 }, ], columns, getRowId: (c) => c.id, messages: EN, locale: 'en-GB', label: 'Comets',});import { Component } from '@angular/core';import { DzGrid, type DzgColumnOrGroup } from '@dzgrid/angular';import { EN } from '@dzgrid/dom/locales/en';
type Comet = { id: string; name: string; seen: string; period: number };
@Component({ selector: 'app-comets', imports: [DzGrid], template: ` <div class="grid-box"> <dz-grid [rows]="rows" [columns]="columns" [getRowId]="rowId" [messages]="messages" locale="en-GB" label="Comets" /> </div>`,})export class Comets { readonly rows: Comet[] = [ { id: 'c1', name: 'Vantor-Elm', seen: '2024-08-19', period: 71.4 }, { id: 'c2', name: 'Ossian Blue', seen: '2025-01-03', period: 6.2 }, { id: 'c3', name: 'Little Harrow', seen: '2023-11-27', period: 13.8 }, ]; readonly columns: DzgColumnOrGroup<Comet>[] = [ { id: 'name', header: 'Comet', flex: 2 }, { id: 'seen', header: 'Last seen', type: 'day', width: 140 }, { id: 'period', header: 'Period (years)', type: 'number', numberFormat: { maximumFractionDigits: 1 } }, ]; readonly rowId = (c: Comet) => c.id; readonly messages = EN;}import '@dzgrid/element/define';import type { DzGridElement } from '@dzgrid/element';import { EN } from '@dzgrid/dom/locales/en';
type Comet = { id: string; name: string; seen: string; period: number };
const grid = document.querySelector<DzGridElement<Comet>>('dz-grid')!;grid.columns = [ { id: 'name', header: 'Comet', flex: 2 }, { id: 'seen', header: 'Last seen', type: 'day', width: 140 }, { id: 'period', header: 'Period (years)', type: 'number', numberFormat: { maximumFractionDigits: 1 } },];grid.getRowId = (c) => c.id;grid.messages = EN;grid.rows = [ { id: 'c1', name: 'Vantor-Elm', seen: '2024-08-19', period: 71.4 }, { id: 'c2', name: 'Ossian Blue', seen: '2025-01-03', period: 6.2 },];Drawn on this page by @dzgrid/dom, the grid <dz-grid> wraps.
columns, getRowId and messages are required (messages may come from provideDzGrid instead, below). Type
the column array DzgColumnOrGroup<T>[]: an untyped literal hides unknown keys.
Inputs are compared by identity
Section titled “Inputs are compared by identity”Inputs are signals. Give a new array or object to change something (rows.update((rs) => [...rs, x])), never a
mutated one. Keep functions (getRowId, rowClass) and the features array as class fields: features is read
once, when the grid is made.
import { createGrid } from '@dzgrid/dom';import { filters } from '@dzgrid/dom/filters-base';import { EN } from '@dzgrid/dom/locales/en';import { EN_FILTERS } from '@dzgrid/dom/locales/en-filters';
type Lamp = { id: number; model: string; watts: number };let rows: Lamp[] = [{ id: 1, model: 'Moth 3', watts: 40 }];
const grid = createGrid<Lamp>(document.getElementById('lamps')!, { rows, columns: [{ id: 'model', header: 'Model' }, { id: 'watts', header: 'Watts', type: 'number' }], getRowId: (l) => l.id, messages: { ...EN, text: { ...EN.text, ...EN_FILTERS.text } }, features: [filters()], selection: { mode: 'multi', checkbox: true }, onEvent: (e) => { if (e.type === 'selectionChanged') console.log(e.count ?? 'some', 'selected'); },});
// A new array, never the old one changed.rows = [...rows, { id: rows.length + 1, model: 'Heron 1', watts: 25 }];grid.update({ rows });import { Component, signal } from '@angular/core';import { DzGrid, type DzgColumnOrGroup } from '@dzgrid/angular';import type { Problem } from '@dzgrid/core';import { filters } from '@dzgrid/dom/filters-base';import { EN } from '@dzgrid/dom/locales/en';import { EN_FILTERS } from '@dzgrid/dom/locales/en-filters';
type Lamp = { id: number; model: string; watts: number };
@Component({ selector: 'app-lamps', imports: [DzGrid], template: ` <button type="button" (click)="add()">Add a lamp</button> <div class="grid-box"> <dz-grid [rows]="rows()" [columns]="columns" [getRowId]="rowId" [messages]="messages" [features]="features" [selection]="{ mode: 'multi', checkbox: true }" [(quick)]="quick" (selectionChanged)="count = $event.count" (problems)="onProblems($event)" /> </div> <p>{{ count ?? 'some' }} selected</p>`,})export class Lamps { readonly rows = signal<Lamp[]>([{ id: 1, model: 'Moth 3', watts: 40 }]); readonly columns: DzgColumnOrGroup<Lamp>[] = [{ id: 'model', header: 'Model' }, { id: 'watts', header: 'Watts', type: 'number' }]; readonly rowId = (l: Lamp) => l.id; readonly messages = { ...EN, text: { ...EN.text, ...EN_FILTERS.text } }; readonly features = [filters()]; quick = ''; count: number | null = 0; add() { this.rows.update((rs) => [...rs, { id: rs.length + 1, model: 'Heron 1', watts: 25 }]); } onProblems(ps: readonly Problem[]) { for (const p of ps) console.warn('dzgrid', p.code, p.path, p.detail); }}import '@dzgrid/element/define';import type { DzGridElement } from '@dzgrid/element';import { filters } from '@dzgrid/dom/filters-base';import { EN } from '@dzgrid/dom/locales/en';import { EN_FILTERS } from '@dzgrid/dom/locales/en-filters';
type Lamp = { id: number; model: string; watts: number };
const grid = document.querySelector<DzGridElement<Lamp>>('dz-grid')!;grid.columns = [{ id: 'model', header: 'Model' }, { id: 'watts', header: 'Watts', type: 'number' }];grid.getRowId = (l) => l.id;grid.messages = { ...EN, text: { ...EN.text, ...EN_FILTERS.text } };grid.features = [filters()];grid.settings = { selection: { mode: 'multi', checkbox: true } };grid.rows = [{ id: 1, model: 'Moth 3', watts: 40 }];
// Properties are compared by identity too: a new array, never the old one changed.grid.rows = [...(grid.rows ?? []), { id: 2, model: 'Heron 1', watts: 25 }];Two-way models: [(quick)], [(filter)], [(selected)], [(expanded)], [(groupBy)]. Left unbound, the grid
owns that state.
App-wide defaults
Section titled “App-wide defaults”provideDzGrid gives every <dz-grid> of the application its texts and default settings. A binding on the tag wins
over a default.
import { ApplicationConfig } from '@angular/core';import { provideDzGrid } from '@dzgrid/angular';import { FR } from '@dzgrid/dom/locales/fr';
export const appConfig: ApplicationConfig = { providers: [provideDzGrid({ messages: FR, defaults: { locale: 'fr-FR', timeZone: 'Europe/Paris', density: 'compact' } })],};The controller
Section titled “The controller”(gridReady) hands over a DzgController: commands, focus, transactions, and each feature’s own api through use.
Without Angular, the grid itself has the same: grid.kernel and grid.api.
import { createGrid } from '@dzgrid/dom';import { dataKey } from '@dzgrid/core';import { EN } from '@dzgrid/dom/locales/en';
type Owl = { id: string; name: string; age: number };
const grid = createGrid<Owl>(document.getElementById('owls')!, { rows: [{ id: 'o1', name: 'Pip', age: 3 }, { id: 'o2', name: 'Bramble', age: 9 }], columns: [{ id: 'name', header: 'Owl' }, { id: 'age', header: 'Age', type: 'number' }], getRowId: (o) => o.id, messages: EN, selection: { mode: 'multi' },});
grid.kernel.dispatch({ type: 'sort.set', sort: [{ column: 'age', dir: 'desc' }] }, 'api');const key = dataKey('o2');if (key) { grid.api.ensureVisible(key, 'name'); grid.kernel.dispatch({ type: 'selection.only', row: key }, 'api');}grid.kernel.rows.applyTransaction({ patch: [{ id: 'o1', set: { age: 4 } }] });import { Component } from '@angular/core';import { DzGrid, DzgController, selectedIds, type DzgColumnOrGroup } from '@dzgrid/angular';import { dataKey } from '@dzgrid/core';import { EN } from '@dzgrid/dom/locales/en';
type Owl = { id: string; name: string; age: number };
@Component({ selector: 'app-owls', imports: [DzGrid], template: `<dz-grid [rows]="rows" [columns]="columns" [getRowId]="rowId" [messages]="messages" [selection]="{ mode: 'multi' }" (gridReady)="ctrl = $event" /> <button type="button" (click)="oldestFirst()">Oldest first</button>`,})export class Owls { readonly rows: Owl[] = [{ id: 'o1', name: 'Pip', age: 3 }, { id: 'o2', name: 'Bramble', age: 9 }]; readonly columns: DzgColumnOrGroup<Owl>[] = [{ id: 'name', header: 'Owl' }, { id: 'age', header: 'Age', type: 'number' }]; readonly rowId = (o: Owl) => o.id; readonly messages = EN; ctrl: DzgController<Owl> | null = null;
oldestFirst() { this.ctrl?.dispatch({ type: 'sort.set', sort: [{ column: 'age', dir: 'desc' }] }); const key = dataKey('o2'); if (key) { this.ctrl?.ensureVisible(key, 'name'); this.ctrl?.dispatch({ type: 'selection.only', row: key }); } this.ctrl?.applyTransaction({ patch: [{ id: 'o1', set: { age: 4 } }] }); const chosen = this.ctrl && selectedIds(this.ctrl); // { mode, ids, query } — ids, ready for a server console.log(chosen); }}import '@dzgrid/element/define';import type { DzGridElement } from '@dzgrid/element';import { dataKey } from '@dzgrid/core';import { EN } from '@dzgrid/dom/locales/en';
type Owl = { id: string; name: string; age: number };
const owls = document.querySelector<DzGridElement<Owl>>('dz-grid')!;owls.columns = [{ id: 'name', header: 'Owl' }, { id: 'age', header: 'Age', type: 'number' }];owls.getRowId = (o) => o.id;owls.messages = EN;owls.settings = { selection: { mode: 'multi' } };owls.rows = [{ id: 'o1', name: 'Pip', age: 3 }, { id: 'o2', name: 'Bramble', age: 9 }];
await owls.ready;// `grid` is the GridInstance while the element is drawn.const grid = owls.grid;const key = dataKey('o2');if (grid && key) { grid.kernel.dispatch({ type: 'sort.set', sort: [{ column: 'age', dir: 'desc' }] }, 'api'); grid.api.ensureVisible(key, 'name'); grid.kernel.dispatch({ type: 'selection.only', row: key }, 'api');}Parts as components
Section titled “Parts as components”A cell (or header, tooltip, editor, detail panel…) can be an Angular component with one signal input named cell.
Instances are reused from row to row: derive everything from cell().
import { ChangeDetectionStrategy, Component, input } from '@angular/core';import { DzGrid, type DzgColumnOrGroup, type DzgPartContext } from '@dzgrid/angular';import { EN } from '@dzgrid/dom/locales/en';
type Robot = { id: string; name: string; state: 'awake' | 'asleep' };
@Component({ selector: 'app-robot-state', changeDetection: ChangeDetectionStrategy.OnPush, template: `<span>{{ cell().text }}</span> <button type="button" (click)="cell().emit('wake', robot().id)">Wake</button>`,})export class RobotState { readonly cell = input.required<DzgPartContext<'cell'>>(); robot(): Robot { return this.cell().row as Robot; }}
@Component({ selector: 'app-robots', imports: [DzGrid], template: `<dz-grid [rows]="rows" [columns]="columns" [getRowId]="rowId" [messages]="messages" (cellAction)="last = $event.name + ' ' + $event.detail" />`,})export class Robots { readonly rows: Robot[] = [{ id: 'r1', name: 'Tock', state: 'asleep' }]; readonly columns: DzgColumnOrGroup<Robot>[] = [{ id: 'name', header: 'Robot' }, { id: 'state', header: 'State', ui: { cell: RobotState } }]; readonly rowId = (r: Robot) => r.id; readonly messages = EN; last = '';}Other ways to give a part: componentPart(slot, Component) in [parts] or provideDzGrid({ parts }); an
<ng-template dzgPart="name" [slot]="'header'" let-c> inside <dz-grid> (import DzgPart); or a plain DOM part
made with definePart from @dzgrid/dom.
Pitfalls
Section titled “Pitfalls”- The TypeScript page, for what the grid does under the component.
- The guides, task by task, starting with columns.