Skip to content
D
Documentation

Angular quick start

tutorial
4 min readUpdated

Mount a working diagram in Angular, then choose the right host and integration components for diagrams, kits, toolbars, and property editing.

The same headless model drives every framework binding; Angular components provide the Angular-shaped way to mount and work with it.

Prerequisites

Use Angular 18.1 through 22. The Angular package's peer dependencies include Angular common, core, forms, and platform-browser, Grafloria engine and renderer, RxJS, and Grafloria element.

Install the Angular binding and its peer packages in your Angular project:

bash
npm install @grafloria/angular @angular/common @angular/core @angular/forms @angular/platform-browser @grafloria/engine @grafloria/renderer rxjs @grafloria/element

1. Mount a canvas

Use DiagramCanvasComponent for a flow editor backed by node and edge data. Its nodes and edges model bindings round-trip edits to the arrays. GrafloriaNodeDefDirective marks a template for nodes of a matching type. The sample includes real data, gives the canvas a height, and turns on the shipped minimap, zoom and fit controls, and background grid.

ts
import { Component } from '@angular/core';
import { DiagramCanvasComponent, GrafloriaNodeDefDirective } from '@grafloria/angular';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [DiagramCanvasComponent, GrafloriaNodeDefDirective],
  template: `
    <grafloria-diagram-canvas
      [(nodes)]="nodes"
      [(edges)]="edges"
      [plugins]="true"
      style="display: block; height: 520px">
      <ng-template grafloriaNode="job" let-data="data">
        <article class="job-card">
          <strong>{{ data['title'] }}</strong>
          <span>{{ data['owner'] }}</span>
        </article>
      </ng-template>
    </grafloria-diagram-canvas>
  `,
  styles: [
    `.job-card {
      box-sizing: border-box;
      height: 100%;
      padding: 12px;
      border: 1px solid #94a3b8;
      border-radius: 8px;
      background: white;
      display: grid;
      align-content: center;
      gap: 6px;
    }`,
  ],
})
export class AppComponent {
  nodes: NodeSpec[] = [
    {
      id: 'extract',
      type: 'job',
      position: { x: 60, y: 90 },
      size: { width: 180, height: 84 },
      data: { title: 'Extract', owner: 'Data team' },
    },
    {
      id: 'publish',
      type: 'job',
      position: { x: 340, y: 90 },
      size: { width: 180, height: 84 },
      data: { title: 'Publish', owner: 'Platform team' },
    },
  ];

  edges: EdgeSpec[] = [
    { id: 'extract-publish', source: 'extract', target: 'publish' },
  ];
}
The mounted canvas shows its graph and plugin controls.

The canvas shows two Angular-rendered job cards connected by an edge, plus its plugin controls and grid. A matching grafloriaNode template is the integration: the canvas routes nodes of that type through Angular's template rendering without a separate node registry.

2. Choose a kit host for a data-first diagram

Use GrafloriaDiagramComponent when a kit builds a complete diagram from a spec. Here erDiagram supplies the entity and relationship data, and <grafloria-diagram> mounts the resulting kit spec. The host also accepts UML and other kit specs through the same spec input.

ts
import { Component } from '@angular/core';
import { GrafloriaDiagramComponent } from '@grafloria/angular';
import { erDiagram } from '@grafloria/element';

@Component({
  selector: 'app-entity-diagram',
  standalone: true,
  imports: [GrafloriaDiagramComponent],
  template: `
    <grafloria-diagram [spec]="spec" style="display: block; height: 520px" />
  `,
})
export class EntityDiagramComponent {
  spec = erDiagram({
    entities: [
      {
        id: 'CUSTOMER',
        name: 'Customer',
        position: { x: 60, y: 80 },
        columns: [
          { name: 'id', type: 'int', pk: true },
          { name: 'name', type: 'varchar' },
        ],
      },
      {
        id: 'ORDER',
        name: 'Order',
        position: { x: 360, y: 80 },
        columns: [
          { name: 'id', type: 'int', pk: true },
          { name: 'customer_id', type: 'int', fk: true },
        ],
      },
    ],
    relationships: [{ from: 'CUSTOMER', to: 'ORDER', label: 'places' }],
  });
}
The kit host displays the entity-relationship diagram.

The mounted kit renders two entity tables joined by a relationship. For a dashboard kit, GrafloriaDashboardComponent is the data-first host; its widget templates use GrafloriaWidgetDefDirective.

3. Find the Angular integrations

These components connect the canvas to custom Angular UI or add editing controls around it. The canvas already mounts its link toolbar when a link is hovered or selected; use the component directly when you need to place that toolbar in your own host.

NeedComponentWhat it provides
Render a node with Angular markupGrafloriaNodeDefDirectiveAn ng-template[grafloriaNode] matched by node type. Its context exposes the live node and its data payload.
Make an HTML node element a connection endpointGrafloriaHandleDirectiveThe grafloriaHandle attribute marks a source or target inside an HTML node; the parent node element needs a data-node-id.
Add contextual actions for a nodeNodeToolbarComponentA floating node toolbar positioned relative to the supplied node. Bind its node and engine.
Edit selected node propertiesPropertyPanelComponentA schema-driven property editor. Set its selectedNodes property on the component instance; the panel needs a property schema for the node type. Its default update mode applies valid changes immediately; deferred mode provides Save and Cancel actions.

These are integrations around the canvas, not alternate diagram hosts. The canvas owns drawing and interaction; custom-node templates add Angular-rendered node content, while toolbars and the property panel provide companion UI. The following standalone examples show each companion component in a mounted Angular view.

Add a node toolbar

Pass the selected live node and the canvas engine to the node toolbar. Set NodeToolbarConfig to keep the toolbar visible without requiring a selection. This sample uses a ToolbarAction to place an Inspect action beside the first node; selecting it writes that node's ID to the browser console.

ts
import { Component } from '@angular/core';
import { DiagramCanvasComponent, NodeToolbarComponent } from '@grafloria/angular';
import type { NodeToolbarConfig, ToolbarAction } from '@grafloria/angular';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

@Component({
  selector: 'app-node-actions',
  standalone: true,
  imports: [DiagramCanvasComponent, NodeToolbarComponent],
  template: `
    <div #host style="position: relative; display: block; height: 360px">
      <grafloria-diagram-canvas
        #canvas
        [(nodes)]="nodes"
        [(edges)]="edges"
        style="display: block; height: 100%" />
      @if (canvas.activeEngine(); as engine) {
        @if (engine.getDiagram()?.getNode('job'); as node) {
          <grafloria-node-toolbar
            [node]="node"
            [engine]="engine"
            [canvasElement]="host"
            [viewport]="canvas.viewport()"
            [zoom]="canvas.zoom()"
            [config]="toolbarConfig"
            [actions]="actions" />
        }
      }
    </div>
  `,
})
export class NodeActionsComponent {
  nodes: NodeSpec[] = [
    { id: 'job', type: 'task', position: { x: 100, y: 110 }, size: { width: 180, height: 80 }, label: 'Review' },
    { id: 'next', position: { x: 360, y: 110 }, size: { width: 160, height: 72 }, label: 'Publish' },
  ];
  edges: EdgeSpec[] = [{ id: 'job-next', source: 'job', target: 'next' }];
  toolbarConfig: NodeToolbarConfig = { behavior: { hideOnMultiSelect: false } };
  actions: ToolbarAction[] = [
    { id: 'inspect', label: 'Inspect', onClick: node => console.info(node.id) },
  ];
}
The Inspect toolbar is positioned beside the canvas node.

The canvas normally supplies link actions itself. Mount LinkToolbarComponent directly when you want a separate toolbar host; this example attaches an Inspect action described by LinkToolbarAction to the live link in a mounted canvas.

ts
import { Component } from '@angular/core';
import { DiagramCanvasComponent, LinkToolbarComponent } from '@grafloria/angular';
import type { LinkToolbarAction } from '@grafloria/angular';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

@Component({
  selector: 'app-link-actions',
  standalone: true,
  imports: [DiagramCanvasComponent, LinkToolbarComponent],
  template: `
    <div #host style="position: relative; display: block; height: 360px">
      <grafloria-diagram-canvas
        #canvas
        [(nodes)]="nodes"
        [(edges)]="edges"
        style="display: block; height: 100%" />
      @if (canvas.activeEngine(); as engine) {
        @if (engine.getDiagram()?.getLink('flow'); as link) {
          <grafloria-link-toolbar
            [link]="link"
            [engine]="engine"
            [canvasElement]="host"
            [viewport]="canvas.viewport()"
            [zoom]="canvas.zoom()"
            [actions]="actions" />
        }
      }
    </div>
  `,
})
export class LinkActionsComponent {
  nodes: NodeSpec[] = [
    { id: 'start', position: { x: 70, y: 120 }, size: { width: 150, height: 70 }, label: 'Start' },
    { id: 'finish', position: { x: 350, y: 120 }, size: { width: 150, height: 70 }, label: 'Finish' },
  ];
  edges: EdgeSpec[] = [{ id: 'flow', source: 'start', target: 'finish' }];
  actions: LinkToolbarAction[] = [
    { id: 'inspect', label: 'Inspect link', onClick: context => console.info(context.link.id) },
  ];
}
The Inspect link toolbar is attached to the rendered edge.

The live link supplies the toolbar's position and its action context. The Inspect button logs the connected link's ID when clicked.

Add a property panel

The property panel needs a schema for the selected node type. Register one with PropertyPanelService before displaying the panel; assign a PropertyDiagramNode to the component instance's selectedNodes property and define its fields with PropertySchema. Its string editor changes the selected node's data immediately.

ts
import { AfterViewInit, Component, viewChild } from '@angular/core';
import { PropertyPanelComponent, PropertyPanelService } from '@grafloria/angular';
import type { PropertyDiagramNode } from '@grafloria/angular';
import type { PropertySchema } from '@grafloria/renderer';

@Component({
  selector: 'app-properties',
  standalone: true,
  imports: [PropertyPanelComponent],
  template: `
    <diagram-property-panel #panel
      [showHeader]="true"
      (propertyChanged)="onPropertyChanged($event.property, $event.value)" />
    <p>Current title: {{ selectedNodes.data['title'] }}</p>
  `,
})
export class PropertiesComponent implements AfterViewInit {
  panel = viewChild.required(PropertyPanelComponent);

  selectedNodes: PropertyDiagramNode = {
    id: 'job',
    type: 'quick-start-task',
    label: 'Review',
    data: { title: 'Review' },
  };

  constructor(propertyPanel: PropertyPanelService) {
    if (!propertyPanel.getSchema('quick-start-task')) {
      const schema: PropertySchema = {
        properties: [{ key: 'title', label: 'Title', editor: 'string' }],
      };
      propertyPanel.registerSchema('quick-start-task', schema);
    }
  }

  ngAfterViewInit(): void {
    this.panel().selectedNodes = this.selectedNodes;
  }

  onPropertyChanged(property: string, value: unknown): void {
    console.info(`${property}: ${String(value)}`);
  }
}
The property panel displays the Title editor for the selected node.

The panel renders a Title editor for the selected node. Editing the field changes its data.title value, which the line below the panel reflects.

Add an interaction settings panel

InteractionConfigPanelComponent needs the live canvas engine. Guard the panel with Angular's @if so it mounts after the canvas provides that engine.

ts
import { Component } from '@angular/core';
import { DiagramCanvasComponent, InteractionConfigPanelComponent } from '@grafloria/angular';
import type { NodeSpec } from '@grafloria/renderer';

@Component({
  selector: 'app-interaction-settings',
  standalone: true,
  imports: [DiagramCanvasComponent, InteractionConfigPanelComponent],
  template: `
    <grafloria-diagram-canvas #canvas [(nodes)]="nodes" style="display: block; height: 320px" />
    @if (canvas.activeEngine(); as engine) {
      <grafloria-interaction-config-panel
        [engine]="engine"
        [expanded]="true"
        (configChanged)="onConfigChanged($event)" />
    }
  `,
})
export class InteractionSettingsComponent {
  nodes: NodeSpec[] = [
    { id: 'task', position: { x: 80, y: 90 }, size: { width: 160, height: 72 }, label: 'Task' },
  ];

  onConfigChanged(config: object): void {
    console.info('Interaction settings changed', config);
  }
}
The expanded interaction settings panel appears with the task canvas.

The expanded settings panel reads and updates interaction configuration on the same engine that drives the visible node.

Show comment threads

GrafloriaCommentPanelComponent uses the comment store created by the canvas. This sample mounts the conversation panel beside a canvas with comments enabled.

ts
import { AfterViewInit, Component, viewChild } from '@angular/core';
import { DiagramCanvasComponent, GrafloriaCommentPanelComponent } from '@grafloria/angular';
import type { NodeSpec } from '@grafloria/renderer';

@Component({
  selector: 'app-comments',
  standalone: true,
  imports: [DiagramCanvasComponent, GrafloriaCommentPanelComponent],
  template: `
    <grafloria-diagram-canvas
      #canvas
      [nodes]="nodes"
      [comments]="true"
      style="display: block; height: 320px" />
    @if (canvas.getCommentStore(); as store) {
      <grafloria-comment-panel [store]="store" />
    }
  `,
})
export class CommentsComponent implements AfterViewInit {
  canvas = viewChild.required(DiagramCanvasComponent);

  nodes: NodeSpec[] = [
    { id: 'note', position: { x: 80, y: 90 }, size: { width: 160, height: 72 }, label: 'Discuss this step' },
  ];

  ngAfterViewInit(): void {
    this.canvas().getCommentStore()?.createThread(
      { kind: 'node', id: 'note' },
      'Should this step include an approval?'
    );
  }
}
The comment panel lists the seeded thread beside the canvas node.

The comment panel receives the canvas's live store and shows the seeded thread anchored to the node.

Identify the lower-level canvas package

CanvasNgCanvasNgComponent belongs to the separate @grafloria/canvas-ng package. It has no inputs or outputs and is not the diagram canvas above; this sample mounts that package's component as-is.

Install that optional package before using this sample:

bash
npm install @grafloria/canvas-ng
ts
import { Component } from '@angular/core';
import { CanvasNgCanvasNgComponent } from '@grafloria/canvas-ng';

@Component({
  selector: 'app-canvas-ng',
  standalone: true,
  imports: [CanvasNgCanvasNgComponent],
  template: `<lib-canvas-ng-canvas-ng style="display: block; min-height: 80px" />`,
})
export class CanvasNgExampleComponent {}
The lower-level component displays its package placeholder text.

This component renders its own package placeholder; use DiagramCanvasComponent to mount a Grafloria diagram.

What you have

You can mount a controlled canvas with typed node and edge data, render custom nodes as Angular templates, and choose the generic kit host when a kit provides the diagram spec. The component map shows which separate integrations add node connections, contextual actions, and property editing.

Where to go next

Was this page helpful?

Angular quick start — Grafloria · GPT-6 Luna