# Angular in 10 minutes

Mount a typed Angular canvas, inspect its engine, and add Grafloria's interaction and property panels.

The first idea is that one headless engine drives every framework binding. The Angular components provide the Angular surface; the engine owns diagram behavior such as interaction and history.

## Prerequisites

Use Angular 18.1 through 22 with a browser application. The package uses standalone components and works with zoneless change detection. You do not need an NgModule or a stylesheet import for this example.

## Install

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

## 1. Mount a typed canvas

Import [`DiagramCanvasComponent`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-angular-components-diagramcanvascomponent#diagramcanvascomponent), and bind typed node and edge arrays.

```ts title="src/app/app.component.ts"
import { Component, viewChild } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import { DiagramEngine } from '@grafloria/engine';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [DiagramCanvasComponent],
  template: `
    <button type="button" (click)="showEngine()">Check engine</button>
    <span>{{ engineStatus }}</span>
    <grafloria-diagram-canvas
      [engine]="engine"
      [(nodes)]="nodes"
      [(edges)]="edges"
      [plugins]="true"
      style="display: block; width: 100%; height: 420px"
    />
  `,
})
export class AppComponent {
  readonly canvas = viewChild.required(DiagramCanvasComponent);
  readonly engine = new DiagramEngine();
  engineStatus = '';

  nodes: NodeSpec[] = [
    {
      id: 'ingest',
      position: { x: 60, y: 120 },
      size: { width: 180, height: 80 },
      data: { label: 'Ingest' },
    },
    {
      id: 'publish',
      position: { x: 380, y: 120 },
      size: { width: 180, height: 80 },
      data: { label: 'Publish' },
    },
  ];

  edges: EdgeSpec[] = [
    { id: 'ingest-to-publish', source: 'ingest', target: 'publish' },
  ];

  showEngine(): void {
    const activeEngine = this.canvas().activeEngine();
    this.engineStatus = activeEngine ? 'Engine is mounted.' : 'Engine is unavailable.';
  }
}
```

You get two connected nodes in a 420-pixel-high editor. `[plugins]="true"` adds the minimap, zoom and fit controls, and background grid. Drag a node, connect ports, pan, or zoom to edit the diagram.

The repository's Angular starter uses the same standalone canvas pattern. See its [source](https://github.com/grafloria/grafloria/blob/ef2bcc55237d4d1f1643b6fea68bd996aa37a9e9/starters/angular) or open the [live Angular demo](https://grafloria.com/angular/).

The [`GrafloriaDiagramComponent`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-angular-components-classes#grafloriadiagramcomponent) is the generic kit host. Use it when a shipped kit, rather than a hand-authored canvas model, defines the diagram:

```ts title="src/app/kit.component.ts"
import { Component } from '@angular/core';
import { GrafloriaDiagramComponent } from '@grafloria/angular';
import { erDiagram } from '@grafloria/element';

@Component({
  selector: 'app-kit',
  standalone: true,
  imports: [GrafloriaDiagramComponent],
  template: `
    <grafloria-diagram
      [spec]="spec"
      style="display: block; width: 100%; height: 420px"
    />
  `,
})
export class KitComponent {
  readonly spec = erDiagram({
    entities: [
      {
        id: 'CUSTOMER',
        name: 'Customer',
        position: { x: 60, y: 80 },
        columns: [{ name: 'id', type: 'int', pk: true }],
      },
      {
        id: 'ORDER',
        name: 'Order',
        position: { x: 360, y: 80 },
        columns: [{ name: 'id', type: 'int', pk: true }],
      },
    ],
    relationships: [{ from: 'CUSTOMER', to: 'ORDER', label: 'places' }],
  });
}
```

This sample renders two table-shaped entities and their relationship in the sized host. The same component accepts the other shipped kit specs.

## 2. Access the mounted engine

The component exposes `activeEngine()` as a signal. It returns the engine bound to the canvas, or the engine the canvas created when you omit `[engine]`. Read it after the view exists; `viewChild.required` supplies the mounted component.

Clicking **Check engine** sets the status to `Engine is mounted.`. The canvas also exposes `undo()` and `redo()` for toolbar buttons; each returns a `Promise<void>` and leaves the rendered diagram in the corresponding history state.

## 3. Add the built-in panels

Import [`InteractionConfigPanelComponent`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-angular-components-interactionconfigpanelcomponent#interactionconfigpanelcomponent) and [`PropertyPanelComponent`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-angular-components-propertypanelcomponent#propertypanelcomponent). Pass the same [`DiagramEngine`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-engine-engine#diagramengine) to the interaction panel. The property panel is schema-driven, so mount it with no selection first; it renders its empty state until you provide selected nodes and a schema for their type.

```ts title="src/app/app.component.ts"
import { Component, viewChild } from '@angular/core';
import {
  DiagramCanvasComponent,
  InteractionConfigPanelComponent,
  PropertyPanelComponent,
} from '@grafloria/angular';
import { DiagramEngine } from '@grafloria/engine';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    DiagramCanvasComponent,
    InteractionConfigPanelComponent,
    PropertyPanelComponent,
  ],
  template: `
    <grafloria-interaction-config-panel
      [engine]="engine"
      [expanded]="true"
    />

    <diagram-property-panel
      [showHeader]="true"
      [updateMode]="'immediate'"
    />

    <grafloria-diagram-canvas
      [engine]="engine"
      [(nodes)]="nodes"
      [(edges)]="edges"
      [plugins]="true"
      style="display: block; width: 100%; height: 420px"
    />
  `,
})
export class AppComponent {
  readonly canvas = viewChild.required(DiagramCanvasComponent);
  readonly engine = new DiagramEngine();
  nodes: NodeSpec[] = [
    { id: 'ingest', position: { x: 60, y: 120 }, size: { width: 180, height: 80 }, data: { label: 'Ingest' } },
    { id: 'publish', position: { x: 380, y: 120 }, size: { width: 180, height: 80 }, data: { label: 'Publish' } },
  ];
  edges: EdgeSpec[] = [{ id: 'ingest-to-publish', source: 'ingest', target: 'publish' }];
}
```

The interaction panel opens expanded and lets the user change interaction mode, port visibility, connection-line style, and advanced toggles at runtime. The property panel is mounted without a selection in this sample. Later, bind `selectedNodes` to a node-shaped value and register a `PropertySchema` for that node type to display editable, validated fields; use `updateMode="deferred"` when changes belong behind Save and Cancel.

## What you have at the end

Your Angular app now has a typed, 100%-wide by 420-pixel-high canvas with two connected nodes, built-in canvas tools, an engine you can reach through the mounted component, a live interaction configuration panel, and the property-panel surface ready for a registered schema.

## Where next

- [How Grafloria works](https://atloria.dev/p/grafloria-h7YM7amryF/developer/how-grafloria-works) explains the engine, model, and bindings.
- [Commands, events, and undo](https://atloria.dev/p/grafloria-h7YM7amryF/developer/commands-events-and-undo) covers the shared history and the engine-level undo path.
- [Auto-layout a diagram](https://atloria.dev/p/grafloria-h7YM7amryF/developer/auto-layout-a-diagram) adds layout and explains when to call it again.
- [Create custom nodes](https://atloria.dev/p/grafloria-h7YM7amryF/developer/create-custom-nodes) replaces default node rendering with Angular templates.
