/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.
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
DrawFlowBaseNode and place input and output connectors. data.type to the Angular component in the core provider. FormControl<DfDataModel> to the editor. 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.
task-1-output-1 . 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.
The same model can branch, merge and use multiple connectors. Positions are world coordinates for the top-left corner of each node wrapper.