Building a browser-based workflow simulator is one of the most useful features for modern enterprise applications. Sectors like manufacturing, aviation, finance, insurance, and IT operations commonly need custom workflows. Users want to visually create steps, connect nodes, define rules, run simulations, and see how the workflow behaves before pushing it to the live environment.
This article explains how to build a production-ready Workflow Simulator using Angular.
The system supports:
Drag-and-drop node creation
Graph-based workflow builder
Branching paths and decision nodes
Node configuration panels
Execution simulation with event logs
Validation of loops, unreachable nodes, and broken paths
Pluggable rule engine
JSON-based storage and replay
Real-time visualisation of execution path
The goal is to build an Angular-based workflow editor that functions like lightweight versions of Camunda Modeler, Node-RED, or n8n, but fully customisable for your product.
Why Build a Workflow Simulator
Typical enterprise systems contain long-running or multi-step business processes. Before executing workflows in production, developers and business users need a safe sandbox to test logic.
A browser-based simulator allows:
Quick iteration
Visual understanding of flows
Troubleshooting of decision logic
Validation before saving workflows
Replaying actual data
Training and demonstration
Most importantly, it reduces rework and production issues.
High-Level Features Required
A workflow simulator must include:
Node palette (Start, Task, Decision, API Call, Script, End).
Drag-and-drop canvas.
Connectors between nodes.
Node configuration (name, rules, conditions, inputs).
JSON export/import of the workflow.
Execution engine to simulate the workflow step-by-step.
Visual highlight of execution path.
Validation engine.
Undo/redo support.
Event logs and breakpoints.
This article focuses on the Angular implementation, core architecture, and execution simulation.
High-Level Architecture
The system is divided into:
Architecture Workflow Diagram
+------------------------------+
| Workflow UI |
+------------------------------+
| | |
| | |
v v v
+------------+ +-------+ +------------+
| Node | | Edge | | Config |
| Palette | | Layer | | Panel |
+------------+ +-------+ +------------+
| |
| Drag | Connections
v v
+---------------------+
| Workflow Canvas |
+----------+----------+
|
v
+------------------------+
| Workflow Engine (Core) |
+----------+-------------+
|
Validate | Simulate
v
+-------------------------+
| Execution Simulator |
+-------------------------+
|
v
+---------------+
| Event Logs |
+---------------+
Flowchart: Workflow Execution Simulation
+--------------------------+
| Load Workflow Definition |
+-------------+------------+
|
v
+-----------+-----------+
| Identify Start Node |
+-----------+-----------+
|
v
+-----------+-----------+
| Execute Current Node |
+-----------+-----------+
|
+-----------+------------+
| Node has outgoing path?|
+------+-----------------+
| Yes
v
+--------+--------+
| Evaluate Rules |
+--------+--------+
|
+---------+--------+
| Select Next Node |
+---------+--------+
|
v
+---------+--------+
| Move to Next Node|
+---------+--------+
|
v
Repeat until End node reached
Designing the Workflow Data Model
A workflow is a directed graph. Each node represents a step, and each edge represents the flow.
Node Structure
export interface WorkflowNode {
id: string;
type: 'start' | 'task' | 'decision' | 'api' | 'script' | 'end';
name: string;
position: { x: number; y: number };
config?: any;
}
Edge Structure
export interface WorkflowEdge {
id: string;
from: string;
to: string;
condition?: string; // expression or rule
}
Workflow Definition
export interface WorkflowDefinition {
nodes: WorkflowNode[];
edges: WorkflowEdge[];
}
Building the Canvas in Angular
You will use these libraries/tools:
Angular CDK DragDrop Module
HTML canvas or SVG for rendering
RxJS for state management
A lightweight graph calculation library (optional)
Basic Canvas Component
@Component({
selector: 'app-workflow-canvas',
templateUrl: './workflow-canvas.component.html',
styleUrls: ['./workflow-canvas.component.css']
})
export class WorkflowCanvasComponent {
@Input() workflow!: WorkflowDefinition;
onNodeDrag(event: CdkDragMove, node: WorkflowNode) {
node.position.x = event.pointerPosition.x;
node.position.y = event.pointerPosition.y;
}
}
Canvas Template
<div class="canvas-area">
<div *ngFor="let node of workflow.nodes"
class="node"
cdkDrag
[style.left.px]="node.position.x"
[style.top.px]="node.position.y">
{{ node.name }}
</div>
<svg class="edge-layer">
<ng-container *ngFor="let edge of workflow.edges">
<line
[attr.x1]="getNode(edge.from).position.x + 50"
[attr.y1]="getNode(edge.from).position.y + 20"
[attr.x2]="getNode(edge.to).position.x"
[attr.y2]="getNode(edge.to).position.y"
stroke="black"
stroke-width="2" />
</ng-container>
</svg>
</div>
Implementing Drag-and-Drop Node Palette
A simple palette:
<div class="palette">
<button (click)="addNode('task')">Add Task</button>
<button (click)="addNode('decision')">Add Decision</button>
<button (click)="addNode('api')">Add API Call</button>
</div>
Add Node Logic
addNode(type: string) {
const newNode: WorkflowNode = {
id: crypto.randomUUID(),
type,
name: `${type} node`,
position: { x: 100, y: 100 }
};
this.workflow.nodes.push(newNode);
}
Adding Connectors Between Nodes
Common approach:
Click first node.
Click second node.
Create edge.
Add Edge Logic
addEdge(fromId: string, toId: string) {
this.workflow.edges.push({
id: crypto.randomUUID(),
from: fromId,
to: toId
});
}
Adding Node Configuration Panel
This allows editing:
Name
Inputs
Conditions
Output fields
API parameters
Decision expressions
Sample Configuration Panel
<div class="config-panel" *ngIf="selectedNode">
<h3>Node Configuration</h3>
<label>Name:
<input [(ngModel)]="selectedNode.name" />
</label>
<label *ngIf="selectedNode.type === 'decision'">
Condition:
<input [(ngModel)]="selectedNode.config.condition" />
</label>
</div>
The Workflow Execution Engine
The Workflow Engine runs the workflow.
This includes:
Engine Structure
export class WorkflowEngine {
constructor(private workflow: WorkflowDefinition) {}
private logs: string[] = [];
execute() {
let currentNode = this.findStartNode();
while (currentNode && currentNode.type !== 'end') {
this.logs.push(`Executing: ${currentNode.name}`);
currentNode = this.getNextNode(currentNode);
}
this.logs.push('Reached end node');
return this.logs;
}
}
Rule Evaluation for Decision Nodes
Rules may be:
Example Rule Evaluation
evaluateCondition(condition: string, context: any): boolean {
const fn = new Function("context", `return ${condition}`);
return fn(context);
}
For large enterprise systems, use a safe rule engine instead of dynamic functions.
Simulating Execution Step-by-Step
Support:
Automatic play
Manual step-by-step
Breakpoints
Step Mode Example
step() {
if (!this.currentNode) {
this.currentNode = this.findStartNode();
return this.currentNode;
}
this.currentNode = this.getNextNode(this.currentNode);
return this.currentNode;
}
Visualising Execution Path
When the simulator runs:
Canvas Highlighting Example
.node.active {
border: 2px solid blue;
}
.edge.active {
stroke: blue;
}
You will toggle CSS classes based on execution logs.
Validations Before Simulation
Essential validations:
Workflow must contain exactly one start node.
All nodes must be reachable from start.
No cycles unless allowed.
Decision nodes must contain valid conditions.
All edges must reference valid node IDs.
Example Validation Function
validateWorkflow(workflow: WorkflowDefinition): string[] {
const errors: string[] = [];
const startNodes = workflow.nodes.filter(n => n.type === 'start');
if (startNodes.length !== 1) {
errors.push('Workflow must contain exactly one start node.');
}
// Additional validations…
return errors;
}
JSON Export and Import
The workflow editor must support saving and loading.
Export
downloadJSON() {
const json = JSON.stringify(this.workflow);
// download logic
}
Import
uploadWorkflow(json: string) {
this.workflow = JSON.parse(json);
}
Undo / Redo Support
Use an RxJS BehaviorSubject to push state snapshots.
Example
history: WorkflowDefinition[] = [];
historyIndex = -1;
pushState() {
this.history.push(JSON.parse(JSON.stringify(this.workflow)));
this.historyIndex++;
}
Undo
undo() {
if (this.historyIndex > 0) {
this.historyIndex--;
this.workflow = JSON.parse(JSON.stringify(this.history[this.historyIndex]));
}
}
Real-World Challenges and Best Practices
Performance drops when rendering more than 200 nodes.
Use canvas or WebGL for large workflows.
Users need zooming and panning.
Implement with CSS transforms.
Complex connectors require bezier curves.
Use SVG path elements.
Decision rules get messy.
Integrate a rules engine (Nools, JSON-rules-engine).
Autosaving is essential.
Persist state in localStorage every few seconds.
Multi-user collaboration.
Use WebSockets and operational transforms.
Prevent invalid node drops.
Add guards and context checks.
Production-Level Recommendations
Use a state management library (NgRx or Akita) for storing workflow state.
Use RxJS Subjects for canvas events.
Debounce drag events to reduce change detection load.
Generate node IDs using crypto APIs for uniqueness.
Allow theming and custom node templates.
Include snap-to-grid behaviour for cleaner diagrams.
Create extension hooks for future custom nodes.
Conclusion
A browser-based workflow simulator is a powerful and reusable tool for any enterprise Angular application. By combining a graph-based canvas, configurable nodes, a robust execution engine, and a validation system, you can build a complete workflow modelling and testing system inside the browser.
What makes this solution truly valuable is its flexibility. It doesn’t depend on any external engine and can be adapted for HR approvals, manufacturing processes, IT automation, financial rules, document processing, and more.