Live demo: ngdiagram.dev/templates/workflow
Interactive workflow / automation editor built with Angular and ngDiagram. Drag triggers, actions, delays, decisions, approvals, merges, notifications and AI agents onto a canvas, connect them and edit their settings in a side panel. Use this project as a starting point for building your own low-code flow designer, automation builder or node-based editor.
There is no backend: Run plays the workflow on an in-browser mock until you plug in your own engine (see Execution). The node types and the bundled workflows are sample data — replace them with your own.
Lean dependencies: Angular, ngDiagram, Phosphor Icons (web font) and html-to-image (for JPEG export) — no opinionated third-party UI libraries.
Features:
- Templates for the common agentic patterns — prompt chaining, routing, parallelization, reflection loop, human-in-the-loop — plus the order-confirmation flow, picked from a "Select a template" dialog or deep-linked with
?template=<id> - Drag-and-drop node placement from a searchable Nodes Library — each tile is a live preview of the node card
- Eight node types, all described by one data-driven catalog
- Custom node templates beyond the default card: Decision / Approval (one output port per branch) and AI Agent (shows the chosen chat model and memory)
- Properties panel built with Angular Signal Forms — fields come from the catalog, can be shown or hidden depending on other values, and changes appear on the node card as you type
- Labelled connections drawn as orthogonal edges with rounded corners; invalid connections are rejected while they are drawn (no self-loops, nothing can connect into a Trigger)
- Graph rule enforced twice: a loop may only close if a Decision or Approval can end it (reflection loops yes, endless agent ping-pong no). While drawing,
linking.validateConnectionrefuses to snap; for the ways an edge arrives without being drawn (paste, template, programmatic edit) a middleware drops just the offending connections and says so in the navbar - A workflow starts in one place: a middleware cancels any drop or paste that would add a second Trigger and says so in the navbar
- Run a workflow on a mocked backend: each step's live status (running, waiting for approval, done, failed) and the path the flow took show on the canvas; Approval steps wait until someone picks Approved / Rejected on the node, and the canvas is locked by a second middleware while a run is in progress
- Right-click context menus — copy / cut / paste / delete on a node, paste on the background
- Export as JSON (nodes and connections) or as a JPEG snapshot of the canvas
- Minimap and zoom controls; nodes snap to an 18px grid
- Dark / light theme driven by a design token system
| Node | Template | Ports | Settings |
|---|---|---|---|
| Trigger | workflow |
output only (start node) | time-based (CRON expression) or event-based (matcher) |
| Action | workflow |
in, out | action type, email fields, API call, retry, simulate failure |
| Delay | workflow |
in, out | delay in milliseconds |
| Decision | decision |
in, one out per branch | branches (add / rename / remove) |
| Approval | decision |
in, one out per branch | approver, channel, timeout; Approved / Rejected branches |
| Merge | workflow |
in (many), out | continue when all branches finish or the first one does |
| Notification | workflow |
in, out | channel, recipient, message |
| AI Agent | ai-agent |
in, out | chat model, memory, system prompt, simulate failure |
Every node also has a Title and a Description.
The library is fully data-driven: every node type is one entry in
node-catalog.ts
describing its label, icon, template, form fields and default values. The palette
tile, the node card and the properties form all read from that entry.
Built against Angular 22 and ngDiagram 1.3 (see package.json); Node.js 22+ and npm.
npm install
npm startOpen http://localhost:4200 — a sample workflow loads: an order-confirmation flow that emails the customer, branches on order value into a sales notification or a delay, then drafts and sends a follow-up with an AI agent (7 nodes, 6 connections). Try dragging a node from the library onto the canvas, wiring it into the flow and editing its settings in the properties panel. Then press Run in the navbar to watch each step report its status.
Templates at the bottom of the Nodes Library swaps in one of the AI pattern workflows. To open one directly, add ?template=<id> to the URL: chaining, routing, parallelization, reflection-loop or human-in-the-loop. Templates are seed data in diagram/templates/.
| Script | Description |
|---|---|
npm start |
Start the dev server (ng serve) |
npm run build |
Production build to dist/ |
npm run watch |
Development build in watch mode |
npm test |
Run unit tests (Vitest) |
npm run lint |
Lint with ESLint (ng lint) |
npm run format |
Format sources with Prettier |
npm run format:check |
Check formatting without writing |
Paths are relative to src/app/workflow-editor/.
| Concern | API | Where in this repo |
|---|---|---|
| Setup | provideNgDiagram(), initializeModel() |
pages/workflow-editor-page.component.ts, template-selector/ |
| Node templates | NgDiagramNodeTemplateMap, NgDiagramNodeTemplate, NgDiagramPortComponent |
diagram/nodes/* |
| Dynamic ports | one ng-diagram-port per decision branch |
diagram/nodes/decision-node/ |
| Edge template + label | NgDiagramEdgeTemplateMap, NgDiagramBaseEdgeComponent, NgDiagramBaseEdgeLabelComponent |
diagram/edges/label-edge/ |
| Connection rules | linking.validateConnection, finalEdgeDataBuilder |
diagram/diagram.component.ts |
| Graph rules | Middleware, createMiddlewares, [middlewares] input, helpers, next({ edgesToRemove }) |
diagram/middlewares/, diagram/model/cycles.ts |
| Read-only while running | helpers.anyNodesAdded / checkIfAnyNodePropsChanged, cancel(), first in the chain |
diagram/middlewares/run-lock.middleware.ts |
| Live run status | node / edge templates reading signals fed by a WorkflowBackend event stream |
execution/, diagram/nodes/shared/node-status.* |
| Routing, snapping | edgeRouting.orthogonal, snapping, background |
diagram/diagram.component.ts |
| Palette | NgDiagramPaletteItemComponent, NgDiagramPaletteItemPreviewComponent |
palette-sidebar/components/palette-tile/ |
| Model updates | NgDiagramModelService.updateNodeData / updateEdgeData / deleteEdges |
properties-sidebar/properties-sidebar.service.ts |
| Replace the model | deleteNodes, transaction({ waitForMeasurements }), addNodes / addEdges, zoomToFit |
template-selector/template-selector.service.ts |
| Selection | NgDiagramSelectionService, selectionGestureEnded, paletteItemDropped |
properties-sidebar/, diagram/ |
| Clipboard | NgDiagramClipboardService |
diagram/editor-actions.service.ts |
| Viewport, minimap | NgDiagramViewportService, NgDiagramMinimapComponent |
minimap-bar/, export/ |
src/
├── styles.css # global styles, imports ng-diagram + Phosphor + theme
├── workflow-theme.css # --wf-* design tokens (light default, dark override) + --ngd-* remap
├── typography.css # Poppins type scale
└── app/workflow-editor/
├── workflow-editor.config.ts # injectable editor config (zoom step, fit padding, grid size)
├── pages/ # page shell: canvas + overlaid panels, provideNgDiagram()
├── diagram/
│ ├── diagram.component.* # ng-diagram host, config, template maps, events
│ ├── templates/ # seed workflows: order flow + one per AI pattern
│ ├── editor-actions.service.ts # copy / cut / paste / delete
│ ├── model/ # types, node catalog, field model, guards, cycle check (+ specs)
│ ├── middlewares/ # graph + run-lock rules on every model change (+ specs)
│ ├── nodes/ # workflow, decision, ai-agent templates + shared header/icon
│ └── edges/label-edge/ # labelled edge template
├── editor-notice.service.ts # short navbar messages for what the editor refused to do
├── execution/ # WorkflowBackend contract, mock backend, run state (+ specs)
├── palette-sidebar/ # Nodes Library + draggable tiles
├── template-selector/ # starting template + "Select a template" dialog, swaps the model in place
├── properties-sidebar/ # panel, Signal Forms for nodes / edges, icon select control
├── minimap-bar/ # zoom stepper + minimap popover
├── context-menu/ # node / background right-click menu
├── export/ # JSON + JPEG export, navbar dropdown
└── top-navbar/ # logo, editable workflow name, run, export, theme toggle
The ngDiagram node.type selects the template, which controls the node's layout. node.data.kind says what the node is. This split lets Trigger, Action, Delay, Notification and Merge share one card template, Decision and Approval share the branching template, and AI Agent gets its own.
node-properties.component.ts works in four steps:
- It copies the selected node's data into a
linkedSignal. - It wraps that copy with
form()from@angular/forms/signals. - It binds each control with
[formField]. The icon dropdown is a custom control that implementsFormValueControl<string>. - An
effectwrites each change back withupdateNodeData.
The field model in field-definitions.ts is deliberately tiny (text, textarea, select, switch, plus a showIf rule) — it shows the catalog-driven form pattern without rebuilding a schema engine.
Every color is a --wf-* token defined per theme in
workflow-theme.css. The base --ngd-* tokens ship
with ngDiagram itself, so that file only adds the --wf-* tokens and remaps the
few --ngd-* variables that drive the on-canvas look.
- Add a node type: add a value to
WorkflowNodeKindinworkflow-types.ts, add a catalog entry (label, icon,template,fields,defaults) and add the kind toPALETTE_ORDER. The palette tile, node card and properties form pick it up automatically. For a custom layout, create a template component, add its key toWorkflowNodeTemplateinworkflow-types.tsand register it inNODE_TEMPLATE_COMPONENTSinnode-templates.ts. The canvas and the palette preview both read that map. - Icons: use
ph-<name>for any Phosphor icon. For your own single-colour SVGs insrc/assets/, usemask:<file>; the icon takes the text colour. - Change the seed workflows: edit or add a file in
diagram/templates/and list it inWORKFLOW_TEMPLATES. - Tune the editor: edit
WORKFLOW_EDITOR_DEFAULTS(zoom-to-fit padding, zoom step, grid size) inworkflow-editor.config.ts, or override theWORKFLOW_EDITOR_CONFIGtoken in the page providers. - Theme: adjust the
--wf-*tokens inworkflow-theme.css.
Run (navbar) sends the same ng-diagram-workflow document the JSON export
produces to a WorkflowBackend and folds the RunEvents it streams back
(node status, edge travelled, finished) into signals the node and edge
templates read.
The page provides
MockWorkflowBackend,
which walks the graph with timers:
- parallel branches run at once
- a Merge waits for all branches or the first one
- a Decision loops back once and then goes forward
- an Approval waits for a click on one of its branches
- Simulate failure fails a step (an Action with Retry on failure fails once, then succeeds)
To connect a real engine, provide another WorkflowBackend in
workflow-editor-page.component.ts.
export.service.ts offers
two formats: a JPEG raster snapshot of the canvas and JSON. The JSON is
an ng-diagram-workflow document — a nodes array (id, kind, position, label,
description, properties, and branches for Decision and Approval nodes) plus a
connections array (port-to-port, with optional labels).
This template is a demo of the editor UI, not a workflow engine. It does not include:
- undo/redo
- persistence or import
- workflow validation beyond connection rules and loop exits
- auto-layout
- real workflow execution — runs are simulated in the browser by
MockWorkflowBackend
- Angular 22 (standalone components, signals, zoneless, Signal Forms)
- ngDiagram (
ng-diagramon npm) - Phosphor Icons (web font)
- html-to-image (JPEG export)
- SCSS component styles and CSS custom-property design tokens (no UI framework)
- Poppins
- Vitest, ESLint, Prettier
This template is the editor UI only. Workflow Builder by Synergy Codes is an open-source React SDK for visual workflow editors that also ships a reference back-end and an execution engine.
- Issues: GitHub Issues
- ngDiagram Discussions: GitHub Discussions, Discord
- ngDiagram Documentation: ngdiagram.dev/docs
MIT — see LICENSE.
Built with ❤️ by the Synergy Codes team
