Mount a typed Angular canvas, bind nodes and edges, and use the canvas and engine APIs from a standalone component.
Grafloria has one headless model behind its framework bindings: the Angular canvas converts your node and edge specs into that model, while the engine provides behavior such as zoom, layout, and history.
Prerequisites
Use Angular 18.1 through 22. The package is standalone and uses signal-based inputs and outputs, so you do not need an NgModule or zone.js provider for the canvas.
Install the packages
bashnpm install @grafloria/angular @grafloria/renderer @grafloria/engine @grafloria/canvas-ng
1. Mount a typed canvas
Import DiagramCanvasComponent and the renderer's NodeSpec and EdgeSpec types. Give the canvas a real height; it fills its container, and a container without a height has no drawable area.
tsimport { Component, signal, viewChild } from '@angular/core';
import {
DiagramCanvasComponent,
GrafloriaNodeDefDirective,
} from '@grafloria/angular';
import type { NodeSpec, EdgeSpec } from '@grafloria/renderer';
import type { DiagramEngine, SerializedDiagram } from '@grafloria/engine';
@Component({
selector: 'app-root',
imports: [
DiagramCanvasComponent,
GrafloriaNodeDefDirective,
],
template: `
<div class="toolbar">
<button type="button" (click)="undo()">Undo</button>
<button type="button" (click)="redo()">Redo</button>
<button type="button" (click)="fit()">Fit</button>
<button type="button" (click)="zoomIn()">Zoom in</button>
<button type="button" (click)="rerunLayout()">Rerun layout</button>
<button type="button" (click)="save()">Save snapshot</button>
<button type="button" (click)="restore()">Restore snapshot</button>
</div>
<grafloria-diagram-canvas
#canvas
[(nodes)]="nodes"
[(edges)]="edges"
[(zoom)]="zoom"
[plugins]="true"
[layout]="'dagre'"
(layoutDone)="onLayoutDone()"
style="display: block; height: 520px">
<ng-template grafloriaNode="job" let-data="data">
<div class="job-card">
<strong>{{ data['title'] }}</strong>
<span>{{ data['owner'] }}</span>
</div>
</ng-template>
</grafloria-diagram-canvas>
`,
styles: [`
.toolbar { display: flex; gap: 8px; margin-bottom: 8px; }
.job-card {
display: flex;
flex-direction: column;
gap: 4px;
height: 100%;
box-sizing: border-box;
padding: 12px;
border: 1px solid #94a3b8;
border-radius: 8px;
background: white;
}
.job-card span { color: #475569; font-size: 12px; }
`],
})
export class AppComponent {
readonly canvas = viewChild.required(DiagramCanvasComponent);
readonly nodes = signal<NodeSpec[]>([
{
id: 'extract',
type: 'job',
position: { x: 60, y: 80 },
size: { width: 180, height: 80 },
data: { title: 'Extract', owner: 'Data team' },
},
{
id: 'publish',
type: 'job',
position: { x: 340, y: 80 },
size: { width: 180, height: 80 },
data: { title: 'Publish', owner: 'Platform team' },
},
]);
readonly edges = signal<EdgeSpec[]>([
{ id: 'extract-publish', source: 'extract', target: 'publish' },
]);
readonly zoom = signal(1);
private saved: SerializedDiagram | null = null;
undo(): void {
void this.canvas().undo();
}
redo(): void {
void this.canvas().redo();
}
fit(): void {
this.canvas().fitToContent(40);
}
zoomIn(): void {
this.zoom.update((value) => value * 1.25);
}
rerunLayout(): void {
void this.canvas().applyLayout();
}
save(): void {
this.saved = this.canvas().snapshot();
}
restore(): void {
if (this.saved) {
this.canvas().loadSnapshot(this.saved);
}
}
onLayoutDone(): void {
const engine = this.canvas().activeEngine();
if (engine) {
engine.getDiagram();
}
}
}
The mounted canvas renders two job cards and an edge. Dragging or connecting updates the two-way nodes and edges signals. [plugins]="true" adds the minimap, zoom and fit controls, and background grid. The job template renders matching nodes in Angular's HTML layer; let-data="data" is each node's payload.
2. Use the canvas and engine
Use the canvas methods for common actions. undo() and redo() return promises, fitToContent() frames the nodes, and snapshot() plus loadSnapshot() round-trip the current document. The activeEngine() signal gives you the live DiagramEngine; use it after the view exists, as in a button handler or another post-mount callback.
The sample's Undo, Redo, Fit, Zoom in, Rerun layout, Save snapshot, and Restore snapshot buttons operate on the mounted diagram. onLayoutDone() runs after the declarative dagre layout completes and obtains the engine's current diagram.
The Angular package also ships these companion components. This complete standalone component shows their selectors and the minimum bindings they accept; the toolbars are hidden until you provide a live selection.
tsimport { CommonModule } from '@angular/common';
import { Component, NgModule } from '@angular/core';
import {
DiagramCanvasComponent,
GrafloriaCommentPanelComponent,
GrafloriaDiagramComponent,
InteractionConfigPanelComponent,
LinkToolbarComponent,
NodeToolbarComponent,
PropertyPanelComponent,
} from '@grafloria/angular';
import { CanvasNgCanvasNgComponent } from '@grafloria/canvas-ng';
import { DiagramEngine } from '@grafloria/engine';
import type { NodeSpec } from '@grafloria/renderer';
@NgModule({
imports: [CommonModule, CanvasNgCanvasNgComponent],
exports: [CanvasNgCanvasNgComponent],
})
export class CanvasNgModule {}
@Component({
selector: 'app-angular-components-example',
imports: [
CanvasNgModule,
DiagramCanvasComponent,
GrafloriaCommentPanelComponent,
GrafloriaDiagramComponent,
InteractionConfigPanelComponent,
LinkToolbarComponent,
NodeToolbarComponent,
PropertyPanelComponent,
],
template: `
<grafloria-diagram-canvas #canvas [comments]="true"
[nodes]="componentNodes"
style="display: block; height: 240px" />
@if (canvas.getCommentStore(); as store) {
<grafloria-comment-panel [store]="store" />
}
<grafloria-diagram [spec]="diagramText"
style="display: block; height: 240px" />
<grafloria-interaction-config-panel
[engine]="panelEngine" [expanded]="true" />
<grafloria-link-toolbar [engine]="panelEngine"
[visible]="false" />
<grafloria-node-toolbar [engine]="panelEngine"
[visible]="false" />
<diagram-property-panel />
<lib-canvas-ng-canvas-ng />
`,
})
export class AngularComponentsExample {
readonly diagramText = '{"nodes":[{"id":"a","label":"Start"},{"id":"b","label":"Finish"}],"edges":[{"source":"a","target":"b"}]}';
readonly componentNodes: NodeSpec[] = [
{ id: 'component-node', position: { x: 40, y: 40 }, label: 'Comments' },
];
readonly panelEngine = new DiagramEngine();
readonly selectedNodes = [];
}
The first canvas enables the comment store, and the comment panel displays that store when it exists. The generic diagram host renders the Mermaid-compatible text. The interaction panel receives an engine, the link and node toolbars provide their action layers when made visible and given targets, the property panel starts with no selected nodes, and the canvas-ng component mounts its lower-level host.
3. Add layout when you need it
Bind [layout] to a registered layout name or request. The binding runs when its value changes, not when node data changes, so a user's drag is not immediately replaced. Call applyLayout() when you want to rerun the bound layout from your own code, and listen to (layoutDone) for completion.
html<grafloria-diagram-canvas
[(nodes)]="nodes"
[(edges)]="edges"
[layout]="'elk'"
(layoutDone)="onLayoutDone()"
style="display: block; height: 520px" />
What you have at the end
You have a standalone Angular component with typed graph data, template-defined nodes, two-way model bindings, built-in canvas tools, undo and redo, snapshot persistence, engine access, and declarative layout.
For the full runnable tutorial and its live demo, open Angular in 10 minutes. Next, read How Grafloria works, then Customize nodes or Apply auto-layout.
Was this page helpful?