Skip to content

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.

Terminal window
npm install @dzgrid/core @dzgrid/dom @dzgrid/theme @dzgrid/angular

In 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; }
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',
});

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 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 });

Two-way models: [(quick)], [(filter)], [(selected)], [(expanded)], [(groupBy)]. Left unbound, the grid owns that state.

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' } })],
};

(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 } }] });

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.

  • The TypeScript page, for what the grid does under the component.
  • The guides, task by task, starting with columns.