ngx-vflow

repository·main·Indexed 19 days ago

https://github.com/artem-mangilev/ngx-vflow

An Angular library for building node-based user interfaces with declarative APIs, subflows, and interactive capabilities. It supports custom edge templates, connection validation, and integration with dagre.js for flowchart layouts. Version compatibility includes v2.x for Angular v19.2.17+, v1.x for Angular v17.3.12+, and v0.x for Angular v16.2.0+.

Tokens
15.1K
Snippets
52
Records
81
Agent score
67%

What's inside ngx-vflow

  1. Overview of ngx-vflow

    main

    ngx-vflow is an Angular library designed for building node-based applications, ranging from static diagrams to complex visual editors. It handles the heavy lifting of flow mechanics, including dragging, zooming, and edge curve mathematics, through a simple API.

    Key characteristics include:

    • Customizable UI: Use standard HTML and CSS to customize nodes. Edges, connection lines, and handles can be customized using SVG.
    • High Performance: Built using Angular Signals to ensure performant rendering, even for large flows.
    • Zoneless Support: The library does not require zone.js to function.
  2. Configure strict vs loose connection modes

    main

    You can control how handles connect using the mode property in ConnectionSettings:

    • Strict Mode ('strict'): The default mode. Connections must strictly adhere to the source and target types defined on the HandleComponent. Connections are restricted to specific directions based on these properties.
    • Loose Mode ('loose'): Ignores the handle type. Any handle can connect to any other handle. Note: When using loose mode, you must provide an id for the HandleComponent to function correctly.
  3. Types of NodeChange and EdgeChange

    main

    The library provides specific change types for both nodes and edges to help you track the lifecycle and state of your flow elements.

    NodeChange Types

    • position: Emitted when a node's position changes after drag and drop.
    • size: Emitted when a node's size changes.
    • add: Emitted when a new node is created.
    • remove: Emitted when a node is removed.
    • select: Emitted when a node is selected (this also triggers for unselected nodes).

    EdgeChange Types

    • add: Emitted when a new edge is created.
    • remove: Emitted when an edge is removed.
    • select: Emitted when an edge is selected (this also triggers for unselected edges).
    • detached: Emitted when an edge becomes invisible because its source or target node is missing. You can use this event to clean up your edges list by deleting these edges.
  4. How subflows work in ngx-vflow

    main

    A subflow is a specialized node that acts as a container for child nodes.

    Key behaviors:

    • Hierarchy: To nest a node inside a subflow, set its parentId property to the unique ID of the subflow node.
    • Coordinate System: Nodes contained within a subflow use coordinates that are relative to the subflow's own position, rather than the global canvas.
    • Connectivity: A subflow is treated as a node itself. If using a template-group type, the subflow can function as a source or target for connections.
    • Customization: You can define custom subflow appearances using the groupNode directive on an ng-template.
  5. Access preselected state in custom templates

    main

    The preselected state is exposed in rendering contexts, allowing you to style the drag-preview state differently from the final selected state.

    • Node Templates: The template context exposes preselected alongside selected.
    • Custom Node Components: If using the base CustomNodeComponent class, you can access the preselected signal.
    • Edge Templates: The edge template context also exposes preselected for explicit rendering of the preselection state.
  6. Migrate to ngx-vflow v2.0 or higher

    main

    When upgrading to version 2.0 or later, several breaking changes were introduced regarding node types, reactivity (Signals), and API naming.

    Node and Edge Reactivity

    In v2.0+, reactive fields in Node and Edge are now implemented using Signals.

    • Nodes: Replace DynamicNode with Node. To handle reactivity, instead of using plain objects, use the factory functions createNode() or createNodes() to ensure fields are correctly wrapped in signals.
    • Edges: Wrap existing edges using createEdge() or createEdges() to convert them to the new signal-based format.

    API Renaming

    Several event and method names have been updated:

    • onConnect $\rightarrow$ connect (Connections API)
    • onNodesChange $\rightarrow$ nodesChanges (Node change events)
    • onComponentNodeEvent $\rightarrow$ componentNodeEvent (Component node events)

    Custom Nodes

    Because DynamicNode was removed, custom node components must now extend CustomNodeComponent instead of CustomDynamicNodeComponent.

  7. Create custom nodes using Angular components

    main

    For complex flows or when you need type-safe data access, you can render nodes directly from Angular standalone components.

    Steps to implement:

    1. Create a standard Angular standalone component.
    2. Crucial: Extend the CustomNodeComponent base class. If you do not extend this class, the library will not render the node.
    3. Pass your specific data interface to the generic of CustomNodeComponent (e.g., CustomNodeComponent<MyDataInterface>) to ensure type-safe access to the data field from the Node definition.
    4. In your node configuration, set the type field to the class name of your new component.

    Benefits:

    • Type-safe access to node data.
    • Ideal for complex flows with diverse node types.

    Limitations:

    • Event management is more complex because nodes are rendered dynamically.
    @Component({
      standalone: true,
      template: `<div>{{ data.label }}</div>`
    })
    export class MyCustomNodeComponent extends CustomNodeComponent<MyDataInterface> {
      // Implementation
    }
    
    // In your node definition:
    const node = {
      id: '1',
      type: MyCustomNodeComponent,
      data: { label: 'Hello' }
    };