Updated for Foblex Flow v19.1.6 (July 2026). The examples below use the unified
fConnectorAPI and preferred current event fields. This tutorial keeps classic, application-owned graph state; managed state is optional.
Live call center example
Historical tutorial source — Foblex Flow 17.7.1
Library
The linked tutorial repository remains the historical 17.7.1 implementation. The snippets in this article are updated for 19.1.6; the live Call Center example above is the maintained v19 reference.
In this tutorial, we will look at the process of creating a visual flow call editor using Angular and the @foblex/flow library.
The first step is to create a new Angular project:
ng new call-workflow-editor
To work with a graph, install @foblex/flow:
ng add @foblex/flow
Defining Models
To manage the components of a workflow, it is necessary to define models:
export interface IFlowModel {
nodes: INodeModel[];
connections: IConnectionModel[];
}
export interface INodeModel {
key: string;
name: string;
outputs: string[];
input?: string;
position: { x: number, y: number };
type: ENodeType;
}
export interface IConnectionModel {
key: string;
from: string;
to: string;
}
Workflow elements configuration
To control the behaviour of our visual workflow call editor, we introduce an enumeration of node types (ENodeType). Our application will use five main types of nodes:
Incoming Call: designed for incoming calls. This is the first node that is activated when a call is received.
Play Text: Allows you to play a preset text to the caller. Can be used for autoresponders or informational messages.
User Input: Waits for input from the user, such as pressing buttons on a phone to navigate through a menu.
To Operator: forwards the call to an operator. Used to contact a live operator if automated options are not suitable.
Disconnect: Ends the call. Can be used after all necessary actions have been completed or if the caller has decided to end the call.
Each node type is assigned unique settings, including name, icon, color, and number of outputs. This allows you to visually distinguish nodes from each other and configure their functionality:
export enum ENodeType {
IncomingCall = 'incoming-call',
PlayText = 'play-text',
UserInput = 'user-input',
ToOperator = 'to-operator',
Disconnect = 'disconnect'
}
export const NODE_MAP = {
[ ENodeType.IncomingCall ]: {
name: 'Incoming call',
icon: 'add_call',
color: '#39b372',
outputs: 1
},
[ ENodeType.UserInput ]: {
name: 'User Input',
icon: 'call_log',
color: '#2676ff',
outputs: 3
},
[ ENodeType.PlayText ]: {
name: 'Play text',
icon: 'wifi_calling_3',
color: '#AF94FF',
outputs: 1
},
[ ENodeType.ToOperator ]: {
name: 'To operator',
icon: 'wifi_calling_3',
color: '#ffb62a',
outputs: 1
},
[ ENodeType.Disconnect ]: {
name: 'Disconnect',
icon: 'phone_disabled',
color: '#ff859b',
outputs: 0
},
};
Editor component
Let's create a component for the visual editor, which will be responsible for visualization and interaction with the workflow:
ng generate component workflow-editor
In this component we will define the workflow display logic and user interaction.
Flow visualization
Displaying call flows is a key part of the editor. To do this, we use the components of the @foblex/flow library. Interaction callbacks run outside Angular’s zone, so this classic-state example replaces object references and calls markForCheck():
@Component({
selector: 'workflow-editor',
templateUrl: './workflow-editor.component.html',
styleUrls: [ './workflow-editor.component.scss' ],
})
export class WorkflowEditorComponent {
private readonly changeDetectorRef = inject(ChangeDetectorRef);
public flow: IFlowModel = {
nodes: [],
connections: []
};
public eConnectableSide = EFConnectableSide;
public cBehavior: EFConnectionBehavior = EFConnectionBehavior.FIXED;
public cType: EFConnectionType = EFConnectionType.SEGMENT;
}
@if(flow) {
<f-flow fDraggable>
<f-canvas fZoom>
@for (connection of flow.connections;track connection.key) {
<f-connection [fConnectionId]="connection.key"
[fBehavior]="cBehavior"
[fType]="cType"
[fSourceId]="connection.from"
[fTargetId]="connection.to">
</f-connection>
}
@for (node of flow.nodes;track node.key) {
<div fNode
fDragHandle
[fNodeId]="node.key"
fConnector
fConnectorType="target"
[fConnectorId]="node.input"
[fConnectorDisabled]="!node.input"
[fConnectorConnectableSide]="eConnectableSide.TOP"
[fNodePosition]="node.position">
<div>{{ node.name }}</div>
@for (output of node.outputs; track output) {
<div fConnector
fConnectorType="source"
[fConnectorId]="output"
[fConnectorMultiple]="false"
[fConnectorConnectableSide]="eConnectableSide.BOTTOM">
</div>
}
</div>
}
</f-canvas>
</f-flow>
}
This will allow us to display the flow, but we need to add the ability to add nodes and connections, since we have nothing to display yet.
Adding new nodes
To make our visual flow call editor more interactive and functional, we provide the user with the ability to add new nodes. This allows you to create more complex and varied call processing scenarios.
public possibleNodes = Object.keys(NODE_MAP).map((key: string) => {
return {
...NODE_MAP[ key ],
type: key
}
});
public onCreateNode(event: FCreateNodeEvent): void {
const outputsCount = NODE_MAP[ event.data ].outputs;
const outputs = Array.from({ length: outputsCount }).map(() => {
return this.generateId();
});
const node: INodeModel = {
key: this.generateId(),
name: NODE_MAP[event.data].name,
outputs,
position: { x: event.externalItemRect.x, y: event.externalItemRect.y },
type: event.data,
};
this.flow = {
...this.flow,
nodes: [...this.flow.nodes, node]
};
this.changeDetectorRef.markForCheck();
}
private generateId(): string {
return `${ Math.random().toString(36).substr(2, 9) }`;
}
Let's add this next to the flow component and add an event to the flow component:
@for (item of possibleNodes;track item) {
<button fExternalItem
[style.color]="item.color"
[fData]="item.type">
{{ item.icon }}
</button>
}
@if(flow) {
<f-flow fDraggable (fCreateNode)="onCreateNode($event)">
...flow content
</f-flow>
}
Users can select nodes from a list and drag them onto the editor workspace using the fExternalItem directive. This action initiates the creation of a new node in the flow with the appropriate visualization and functionality settings.
Editing connections
After adding nodes to the flow, the next important step is creating connections between them. Connections determine the logic of transition from one node to another and form the final flow of call processing.
To edit and create new connections, we have provided the ability to visually interact with node components:
<f-flow fDraggable
(fCreateConnection)="onCreateConnection($event)"
(fReassignConnection)="onReassignConnection($event)">
<f-canvas fZoom>
<f-connection-for-create></f-connection-for-create>
...flow content
</f-canvas>
</f-flow>
public onCreateConnection(event: FCreateConnectionEvent): void {
if (!event.targetId) {
return;
}
const connection: IConnectionModel = {
key: this.generateId(),
from: event.sourceId,
to: event.targetId
};
this.flow = {
...this.flow,
connections: [...this.flow.connections, connection]
};
this.changeDetectorRef.markForCheck();
}
public onReassignConnection(event: FReassignConnectionEvent): void {
this.flow = {
...this.flow,
connections: this.flow.connections.map((connection) => {
if (connection.key !== event.connectionId) {
return connection;
}
if (event.endpoint === 'source' && event.nextSourceId) {
return { ...connection, from: event.nextSourceId };
}
if (event.endpoint === 'target' && event.nextTargetId) {
return { ...connection, to: event.nextTargetId };
}
return connection;
})
};
this.changeDetectorRef.markForCheck();
}
This allows users to easily modify an existing flow by adding new logical connections or changing existing ones. This approach makes the process of setting up flow calls flexible and intuitive.
Live example · Historical tutorial source (17.7.1) · Library


Top comments (1)
can we get the step by step guide on how to use this call center flow?