/

Getting Started

Build a working Angular graph from one custom node component, one immutable data model and one reactive form control. Core owns the editor surface and interactions; your application owns node content and graph data.

Angular componentsRender any domain UI inside a node.
Reactive FormsRead and replace the complete graph as one value.
Application-owned dataPersist, validate and synchronize plain node and connection objects.

Install Core

NgDrawFlow requires Angular 19 or later. Install the core package, then import ReactiveFormsModule in the standalone component that owns the graph.

    
      npm install @ng-draw-flow/core
    

Build the First Graph

  1. Render a node Extend DrawFlowBaseNode and place input and output connectors.
  2. Register its type Map data.type to the Angular component in the core provider.
  3. Bind the model Pass a non-nullable FormControl<DfDataModel> to the editor.

1. Custom Node Component

Connector placement belongs to the custom node. The example hides the input for a start node and the output for an end node, and uses signal inputs from the base class.

    
      
    
    
      import {ChangeDetectionStrategy, Component} from '@angular/core';
import {DfInputComponent, DfOutputComponent, DrawFlowBaseNode} from '@ng-draw-flow/core';

@Component({
    standalone: true,
    selector: 'app-your-node',
    imports: [DfInputComponent, DfOutputComponent],
    templateUrl: './node.template.html',
    styleUrl: './node.styles.less',
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class YourNodeComponent extends DrawFlowBaseNode {}

    
    
      :host {
    display: block;
    inline-size: 10rem;
}

.input,
.output {
    position: absolute;
    z-index: 1;
}

.input {
    inset-inline-start: -0.5rem;
    inset-block-start: 0.25rem;
}

.output-wrapper {
    position: relative;
}

.output {
    inset-inline-end: -0.5rem;
    inset-block-start: 0;
}

    
    
      @if (!startNodeSignal()) {
    <df-input
        class="input"
        [connectorData]="{nodeId: nodeIdSignal(), connectorId: nodeIdSignal() + '-input-1', single: false}"
    />
}

<p class="tui-text_body-xs">{{ modelSignal().text }}</p>

@if (!endNodeSignal()) {
    <df-output
        class="output"
        [connectorData]="{nodeId: nodeIdSignal(), connectorId: nodeIdSignal() + '-output-1', single: false}"
    />
}

    
Connector ids must be unique across the complete graph. Prefix them with the node id, for example task-1-output-1 .

2. Standalone Editor Component

This complete component registers the node, configures the editor, defines two positioned nodes and connects their rendered connector ids.

graph-editor.component.ts

    
      import {ChangeDetectionStrategy, Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {
  DfArrowhead,
  DfConnectionPoint,
  DfConnectionType,
  type DfDataModel,
  NgDrawFlowComponent,
  provideNgDrawFlowConfigs,
} from '@ng-draw-flow/core';

import {YourNodeComponent} from './your-node.component';

@Component({
  standalone: true,
  selector: 'app-graph-editor',
  imports: [NgDrawFlowComponent, ReactiveFormsModule],
  template: `
    <ng-draw-flow [formControl]="form" />
  `,
  providers: [
    provideNgDrawFlowConfigs({
      nodes: {task: YourNodeComponent},
      connection: {
        type: DfConnectionType.SmoothStep,
        arrowhead: {type: DfArrowhead.ArrowClosed},
      },
    }),
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GraphEditorComponent {
  readonly form = new FormControl<DfDataModel>(
    {
      nodes: [
        {
          id: 'task-1',
          data: {type: 'task', text: 'Plan'},
          position: {x: 0, y: 0},
          startNode: true,
        },
        {
          id: 'task-2',
          data: {type: 'task', text: 'Build'},
          position: {x: 280, y: 80},
          endNode: true,
        },
      ],
      connections: [
        {
          source: {
            nodeId: 'task-1',
            connectorId: 'task-1-output-1',
            connectorType: DfConnectionPoint.Output,
          },
          target: {
            nodeId: 'task-2',
            connectorId: 'task-2-input-1',
            connectorType: DfConnectionPoint.Input,
          },
        },
      ],
    },
    {nonNullable: true},
  );
}
    

User interactions write a new value to the control. For application-side changes, create the next immutable model and call form.setValue(nextModel) . Disable the control with form.disable() when the graph should remain visible but non-interactive.

3. Working Result

The same model can branch, merge and use multiple connectors. Positions are world coordinates for the top-left corner of each node wrapper.

Where to Go Next

Custom nodesTyped node data, reactive content and wrapper styling.
ConnectorsMultiple handles, custom content and connection constraints.
Dynamic layoutsOptional strict-tree positioning with measured node sizes.