/ng-draw-flow is a ControlValueAccessor. Use an Angular form control as the source of truth, component outputs for local template reactions, and NgDrawFlowStoreService when controls or observers live outside the editor component.
DfDataModel as immutable application state. Replace node, connection and position objects when updating the control so Angular and the editor can reconcile them predictably. Select and drag nodes, select an edge, use the camera controls, add a node, or remove the selected node. The inspector reads signals from the store while the event list is populated from component outputs.
Use outputs when the editor and its consumer share a template. Mutation events contain both the affected item and the complete resulting model through DfEvent<T> . Selection events return the selected item directly.
editor.component.html
<ng-draw-flow
[formControl]="form"
(scale)="onScale($event)"
(nodeSelected)="onNodeSelected($event)"
(connectionSelected)="onConnectionSelected($event)"
(nodeMoved)="onNodeMoved($event)"
(nodeDeleted)="onNodeDeleted($event)"
(connectionCreated)="onConnectionCreated($event)"
(connectionDeleted)="onConnectionDeleted($event)"
/>
scale : current zoom percentage, where 100 is actual size. nodeSelected : selected DfDataNode . connectionSelected : selected DfDataConnection . nodeMoved and nodeDeleted : DfEvent<DfDataNode> . connectionCreated and connectionDeleted : DfEvent<DfDataConnection> . The root-scoped store mirrors live editor state. Every long-lived snapshot is available as an Angular signal and, where useful, as an RxJS stream. Event streams have a matching last-event signal for computed state and OnPush templates.
toolbar.component.ts
readonly store = inject(NgDrawFlowStoreService);
readonly selectedTitle = computed(
() => this.store.selectedNode()?.data['title'] ?? 'Nothing selected',
);
readonly canDelete = this.store.hasSelection;
zoomIn(): void {
this.store.zoomIn();
}
addNode(): void {
const model = this.store.dataModel();
if (!model) {
return;
}
this.store.setDataModel({
...model,
nodes: [
...model.nodes,
{
id: crypto.randomUUID(),
data: {type: 'task', title: 'New task'},
position: {x: 0, y: 0},
},
],
});
}
dataModel / dataModel$selectedNode / selectedNode$selectedConnection / selectedConnection$scale / scale$ and hasSelectionlastNodeMoved , lastNodeDeleted and lastNodeSelectedlastConnectionCreated , lastConnectionDeleted and lastConnectionSelectedzoomIn() , zoomOut() , resetPosition() , setPosition() , setScale() , setDataModel() , removeNode() , removeConnection() and setDataModel() forward to the attached editor. Model replacement preserves node selection by node id and connection selection by source and target connector endpoints while those items still exist.
Read scale signals and events as percentages, but pass a factor to setScale() and the optional setPosition().zoom field. For example, read 75 for 75% and write 0.75 to restore it.
Disable the bound form control to disable editor interaction while keeping the current graph rendered: form.disable() .
A root-scoped store controls the currently attached editor. When a page contains independent editors, scope one store instance to each editor host through component providers.
editor-host.component.ts
@Component({
// The editor and every child toolbar resolve this local instance.
providers: [NgDrawFlowStoreService],
template: `
<editor-toolbar />
<ng-draw-flow [formControl]="form" />
`,
})
export class EditorHostComponent {}