DEV Community

Cover image for Inside Foblex Flow — Part 1: Library Architecture and Design Principles
Siarhei Huzarevich
Siarhei Huzarevich

Posted on • Edited on • Originally published at Medium

Inside Foblex Flow — Part 1: Library Architecture and Design Principles

Updated for Foblex Flow v19.1.6 (July 2026). Foblex Flow still defaults to classic, application-owned graph state. The optional provideFFlow(withFlowState()) mode now adds typed managed records, snapshots, and undo/redo without taking ownership of domain semantics or persistence.

Node-based interfaces are everywhere today: low-code platforms, workflow editors, chatbot builders, business automation systems, ETL tools. Visual logic modeling allows users to “assemble a program” without code by dragging blocks and connecting them with arrows.

But if you work with Angular, you’ll quickly face a problem:

  • most popular solutions are React-only (e.g., React Flow),
  • others are tightly coupled to their own data model,
  • or they feel outdated and don’t fit into modern Angular projects.

That’s why we built Foblex Flow — a library that fills this gap.

  • Written entirely in Angular.
  • Doesn’t dictate how to store your data.
  • Provides a flexible interaction layer.
  • Based on a clear, event-driven API.

In this series, we’ll look inside the library — how it works, which principles guide its design, and which architectural decisions make it lightweight and universal.

We’ll start with the foundation: architecture and design principles.

https://github.com/Foblex/f-flow

🎯 The Key Idea: Separation of Responsibilities

Most node-based libraries give you everything “out of the box”: a data store, JSON graph, APIs for saving and loading. That’s convenient at first, but becomes a limitation once you try to integrate it into a real application.

Foblex Flow makes the state mode explicit:

  • 👉 Classic mode (default): your application owns graph records; Foblex renders them and emits interaction events.
  • 👉 Managed mode (opt-in): provideFFlow(withFlowState()) supplies typed records, snapshots, and undo/redo.
  • 👉 In both modes: your application owns domain fields, validation, permissions, persistence, and business meaning.

What the library stores in classic mode

  • Node positions (x, y) — tracked for rendering; unbound nodes start at { x: 0, y: 0 }.
  • Canvas scale and translate — zoom and panning (defaults: scale = 1, translate = {0,0}).
  • Drag session state — current node coordinates, cursor offsets.
  • Element selection (selected / unselected) — for highlighting and frames.
  • Internal UI states — e.g., snap highlight when connecting nodes.

What the user stores

  • Graph structure — which nodes exist, their types and parameters.
  • Connection model — which links are allowed, validation rules.
  • Node semantics — what a “Webhook”, “AI Generator” or “Parser” node actually does.
  • Persistence — saving/loading flows (NgRx, Signals, IndexedDB, Firebase, etc.).
  • Visual styles — how nodes and connectors look.

Managed mode additionally stores the graph records you explicitly load into FFlowState, plus selection, viewport snapshots, and history. It remains opt-in and does not define your domain model or persistence contract.

📌 In short: Foblex handles the editor interaction layer; your application keeps ownership of business meaning in both modes.

⚡ Event-Driven Model

The golden rule of Foblex Flow is explicit state ownership. In classic mode, interaction events tell your application what happened. In managed mode, only supported completed gestures update the FFlowState instance that you explicitly install.

Current interaction events include:

  • fMoveNodes — one or more nodes finished moving.
  • fSelectionChange — node, group, or connection selection changed.
  • fCreateConnection — a connection gesture completed.
  • fReassignConnection — an existing connection endpoint was moved.
  • fDeleteSelected — deletion of the current selection was requested.
  • fCanvasChange — the canvas transform changed.

In classic mode, every action is a signal to your application:

“This happened. You decide what to do.”

That makes the library predictable and easy to integrate with any state management — from Signals to NgRx.

🧩 Architectural Layers

We can break down Foblex Flow into four layers:

FFlowComponent
 └── FCanvas
      ├── FNodeDirective
      │     └── FConnectorDirective
      │           ├── source
      │           ├── target
      │           └── source-target
      │
      └── FConnectionComponent
Enter fullscreen mode Exit fullscreen mode

Flow
The root component f-flow — coordinates context, events, and canvas state.

Canvas
The workspace: holds all nodes and connections. Manages zoom, pan, and selections.

Nodes
Nodes are not predefined components, but directives you can attach to any Angular element.

  • fNode — turns an element into a node.
  • fConnector — defines a unified connector with fConnectorType="source", "target", or "source-target". The legacy fNodeOutput and fNodeInput directives remain available in v19 but are deprecated.

Connections
An SVG path whose fSourceId and fTargetId reference rendered fConnectorId values. It supports Bézier, straight, segmented, and adaptive connection types.

🔧 Minimal Example

<f-flow fDraggable>
  <f-canvas>
    <f-connection fSourceId="out1" fTargetId="in1"></f-connection>
    <f-connection-for-create></f-connection-for-create>

    <div fNode fDragHandle fNodeId="node1" [fNodePosition]="{ x: 100, y: 150 }">
      <div fConnector fConnectorType="source" fConnectorId="out1"></div>
    </div>

    <div fNode fDragHandle fNodeId="node2" [fNodePosition]="{ x: 400, y: 150 }">
      <div fConnector fConnectorType="target" fConnectorId="in1"></div>
    </div>
  </f-canvas>
</f-flow>
Enter fullscreen mode Exit fullscreen mode

fNodePosition defaults to { x: 0, y: 0 }; bind a point object so nodes do not overlap at the origin. In classic mode, where and how you persist positions (Signals, NgRx, Firestore, or a service) remains entirely up to you.

🛠 Design Principles

1. Minimal Magic
Instead of hiding complexity behind black boxes, the API is transparent: directives and events are explicit.

2. SSR and Zoneless Angular
Foblex Flow supports Angular SSR. Browser-only paths are guarded so they do not execute during server rendering.

3. Performance

  • Classic mode has no library-owned global JSON graph; managed mode keeps only the typed records you opt into.
  • Scopes many registry updates and connection redraws to affected geometry, while explicit full-redraw APIs remain available.
  • Drag-and-drop is optimized for large graphs (hundreds of nodes).

4. Flexibility

  • Store flows as JSON if you like.
  • Sync with Firebase or GitHub.
  • Save to IndexedDB.

The library never dictates your persistence strategy.

📐 Visual Diagram

flowchart TD
    subgraph "Library stores"
        A[Node positions] --> B[Scale/Translate]
        B --> C[Selections]
        C --> D[Drag sessions]
    end

    subgraph "Application stores"
        E[Graph structure]
        F[Connections]
        G[Node parameters]
        H[Persistence]
    end

    A -.-> E
    B -.-> F
    C -.-> G
    D -.-> H
Enter fullscreen mode Exit fullscreen mode

The diagram illustrates the classic-mode boundary. Managed mode can hold graph records and history, but your app still owns domain fields, validation, persistence, and business logic.

🧠 Philosophy: UI Handles Interactivity, Not Data

The core philosophy: Foblex Flow is an editor toolkit, not a “platform inside a platform.”

It focuses on what it does best — interactive UI.

  • Classic mode tells your app what the user did; managed mode writes supported gestures to the state controller you opted into.
  • Your app decides what every graph record means.

This separation makes the library universal. You can build anything — from workflow editors to no-code chatbot builders.

🚀 Conclusion

Foblex Flow is not a black box — it’s a transparent tool for building node-based UIs in Angular.

  • Classic mode keeps graph records application-owned; managed mode is an explicit opt-in for records and history.
  • Both modes leave business logic and persistence under your control.
  • Fits projects of any scale, from simple diagrams to complex low-code platforms.

In the next part, we’ll dive into the drag-and-drop engine:

  • unifying mouse, touch, and pointer events,
  • the internal IPointerEvent structure,
  • how FDraggableDirective works,
  • and how plugins enable extensions like resize, rotate, and external drop.

Top comments (0)