# Angular quick start

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

```bash
npm install @grafloria/angular @grafloria/renderer @grafloria/engine @grafloria/canvas-ng
```

## 1. Mount a typed canvas

Import [`DiagramCanvasComponent`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-angular-components-diagramcanvascomponent#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.

```ts title="src/app/app.component.ts"
import { 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`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-engine-engine#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.

```ts title="src/app/angular-components-example.component.ts"
import { 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](https://grafloria.com/learn/angular/). Next, read [How Grafloria works](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/how-grafloria-works), then [Customize nodes](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/customize-nodes) or [Apply auto-layout](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/apply-auto-layout).
