/ Combine local node validity with Angular validators on the complete DfDataModel . Core maps validator node ids back to rendered nodes and applies the standard invalid wrapper state.
Override the protected invalidState getter to combine local UI validity with invalidSignal() supplied by graph validators. Core reads the result from the custom component and applies .df-invalid to the editor node wrapper.
form-node.component.ts
@Component({
standalone: true,
selector: 'app-form-node',
imports: [ReactiveFormsModule],
template: `
<label>
Task name
<input [formControl]="name" />
</label>
`,
host: {
'(keydown.delete.stop)': '0',
'(keydown.backspace.stop)': '0',
},
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FormNodeComponent extends DrawFlowBaseNode {
readonly name = new FormControl('', {
nonNullable: true,
validators: [Validators.required],
});
protected override get invalidState(): boolean {
const localInvalid = this.name.touched && this.name.invalid;
return this.invalidSignal() || localInvalid;
}
}
Attach validators to the same non-nullable form control bound to ng-draw-flow . They receive the complete graph value and participate in normal Angular form status and errors.
editor.component.ts
readonly form = new FormControl<DfDataModel>(initialModel, {
nonNullable: true,
validators: [
dfCycleDetectionValidator(),
dfIsolatedNodesValidator(),
],
});
| Validator | Checks | Node ids |
|---|---|---|
dfCycleDetectionValidator() | Directed cycles in the connection structure. | cycleNodes |
dfIsolatedNodesValidator() | Nodes absent from every source and target endpoint. | isolatedNodes |
cycle-error.ts
{
hasCycle: boolean;
cycleNodes: DfId[]; // array of nodeIds that form a cycle
}
isolated-nodes-error.ts
{
hasIsolatedNodes: boolean;
isolatedNodes: DfId[] // array of nodeIds without connections
}
Implement a standard Angular ValidatorFn and keep it pure. Return affected ids either as a top-level string array value or in a nested {nodeIds: string[]} object so the editor can highlight those nodes.
required-root.validator.ts
export function requiredRootValidator(): ValidatorFn {
return (control): ValidationErrors | null => {
const model = control.value as DfDataModel;
const roots = model.nodes.filter((node) => node.startNode === true);
return roots.length === 1 ? null : {requiredRoot: {nodeIds: model.nodes.map(({id}) => id)}};
};
}
The built-in cycle validator caches by connection structure. Isolated-node validation also reads the node list, so replacing graph arrays and objects remains the predictable update model.