Relinking
since v1.4.0The edge relinking feature lets users reconnect existing edges by dragging their endpoints. A selected edge shows a handle at each end that can be relinked. Dragging a handle previews the new connection live and commits it on drop.
Select an edge to see its handles: the first edge can be relinked at both ends, the second only at its target end, and the third is locked. Drag a handle to another port to reconnect the edge, or drop it on empty canvas to detach that end.
Enabling Relinking
Section titled “Enabling Relinking”Relinking is off by default. linking.defaultRelinkable sets the default for every edge: true lets users drag both ends, while 'source' or 'target' lets them drag only that end.
config = {6 collapsed lines
zoom: { zoomToFit: { onInit: true, padding: 80, }, }, linking: { // Both ends of every edge can be relinked, unless the edge sets its own `relinkable` defaultRelinkable: true, }, // A handle dropped on empty canvas detaches that end instead of reverting the relink danglingEdges: { enabled: true, }, } satisfies NgDiagramConfig;With defaultRelinkable: true, every selected edge rendered by ng-diagram-base-edge shows a handle at each end. The preview of a new edge that is being drawn never shows handles.
Controlling Which Edges Can Be Relinked
Section titled “Controlling Which Edges Can Be Relinked”An edge can override the default with its own relinkable property. The effective value is the edge’s relinkable when it is set, otherwise linking.defaultRelinkable. This value decides which ends the user can drag:
| Effective value | Source end | Target end |
|---|---|---|
true | yes | yes |
'source' | yes | no |
'target' | no | yes |
false | no | no |
Edges in the initial model, edges added with addEdges, and pasted edges take relinkable from their own data:
model = initializeModel({8 collapsed lines
nodes: [ { id: 'a', position: { x: 100, y: 60 }, data: { label: 'A' } }, { id: 'b', position: { x: 550, y: 60 }, data: { label: 'B' } }, { id: 'c', position: { x: 100, y: 190 }, data: { label: 'C' } }, { id: 'd', position: { x: 550, y: 190 }, data: { label: 'D' } }, { id: 'e', position: { x: 100, y: 320 }, data: { label: 'E' } }, { id: 'f', position: { x: 550, y: 320 }, data: { label: 'F' } }, ], edges: [ { id: 'both-ends', source: 'a', sourcePort: 'port-right', target: 'b', targetPort: 'port-left', // No `relinkable`: the config default applies data: { label: 'both ends' }, }, { id: 'target-only', source: 'c', sourcePort: 'port-right', target: 'd', targetPort: 'port-left', relinkable: 'target', data: { label: 'target end only' }, }, { id: 'locked', source: 'e', sourcePort: 'port-right', target: 'f', targetPort: 'port-left', relinkable: false, data: { label: 'locked' }, }, ], });Edges drawn by the user get the value from finalEdgeDataBuilder, the same callback where an app assigns their type. To make only one edge type relinkable, keep the default at false and set relinkable: true for that type:
const config: NgDiagramConfig = { linking: { // defaultRelinkable stays false: only edges with relinkable: true can be relinked finalEdgeDataBuilder: (edge) => ({ ...edge, type: 'draft', relinkable: true }), },};For the opposite case, where every edge is relinkable except a few locked ones, set defaultRelinkable: true and put relinkable: false on the locked edges.
The builder runs only for edges that the user draws. Loaded edges use the default or their own relinkable property.
How It Works
Section titled “How It Works”- Press a handle and drag it. The endpoint follows the pointer as a live preview and snaps to nearby ports, the same as when drawing an edge.
- A click on a handle without dragging (less than about 5 px of pointer movement) does nothing: no relink starts and no events fire.
- Pressing Escape (or calling
cancelActiveInteraction()) cancels the relink, and the edge stays unchanged.
Drop Outcomes
Section titled “Drop Outcomes”What happens on release depends on where the endpoint is dropped:
- On a valid port — the edge is reconnected to that node and port.
- On empty canvas — the endpoint becomes a free (dangling) endpoint anchored at the drop position. This requires
danglingEdges.enabledto be true andshouldKeepOnDropto keep the edge. A canvas drop is not a connection, sovalidateConnectionis not called for it. With dangling edges disabled, or whenshouldKeepOnDropreturnsfalse, the relink is reverted with reasonnoTarget. - On an invalid target — for example a hidden node, a missing port, a port with the wrong direction, or a connection that the validator refuses — the relink is reverted with reason
invalidConnection. - Back on the original port — nothing changes: the model stays as it is, and the gesture reports
success: falsewith reasoncancelled.
A reverted relink never changes the model. The edge stays as it was.
Validating Reconnections
Section titled “Validating Reconnections”Connections made by relinking go through the same linking.validateConnection callback as edge drawing. The optional fifth argument, a ConnectionValidationContext, tells you which operation is being validated. For relinks it carries reason: 'relink', the edge being relinked, and the end that is being dragged:
const config: NgDiagramConfig = { linking: { validateConnection: (source, sourcePort, target, targetPort, context) => { if (source?.id === target?.id) { return false; // no self-connections } if (context?.reason === 'relink') { // A relinked end stays inside the edge's current group return source?.groupId === target?.groupId; } return true; }, },};Use context.reason === 'relink' for rules about the drop target, like the same-group rule above. To lock an edge or one of its ends, use relinkable instead. The validator runs only on drop, so an edge locked through the validator would still show handles and let the user drag them.
Note that source or target can be null. This happens when the other end of the relinked edge is free, for example when the user relinks the target end of an edge whose source end is dangling. source is also null for draws started with startLinkingFromPosition. Guard against null in your validator.
Events
Section titled “Events”Two events report the gesture:
edgeRelinkStarted— fires when the user starts dragging an endpoint. The payload carries theedge(a snapshot taken at gesture start) and theendthat is being dragged.edgeRelinkEnded— fires when the gesture ends, whatever the outcome. The payload carries:edge— the edge after the relink, or the unchanged snapshot on failureend— the dragged endpointpreviousNodeandpreviousPortwhen the endpoint was connected before, orpreviousPositionwhen it was freedropPositionandsuccess- on a reconnect,
targetandtargetPortname the new connection; on failure,reasonis one ofnoTarget|invalidConnection|cancelled
<ng-diagram (edgeRelinkStarted)="onRelinkStarted($event)" (edgeRelinkEnded)="onRelinkEnded($event)" />onRelinkEnded(event: EdgeRelinkEndedEvent) { if (event.success && event.target) { console.log(`Reconnected ${event.end} of ${event.edge.id} from ${event.previousNode?.id} to ${event.target.id}`); } else if (event.success) { console.log(`Detached ${event.end} of ${event.edge.id} at`, event.dropPosition); } else { console.log('Relink reverted:', event.reason); }}Customization
Section titled “Customization”Relink Handles
Section titled “Relink Handles”A handle is a ring at each end of the edge. A handle shows a halo on hover and is filled while its end is dragged. The handles stay visible during the drag.
Here are the CSS variables you can use to style the relink handles globally:
--ngd-relink-handle-size // Outer diameter of the handle (the stroke is inside) --ngd-relink-handle-stroke-width // Stroke width of the handle --ngd-relink-handle-fill // Fill color of the handle --ngd-relink-handle-fill-hover // Fill color on hover --ngd-relink-handle-fill-drag // Fill color while the end is dragged --ngd-relink-handle-stroke // Stroke (outline) color of the handle --ngd-relink-handle-stroke-hover // Stroke color on hover --ngd-relink-handle-stroke-drag // Stroke color while the end is dragged --ngd-relink-handle-halo-size // Width of the halo around the handle --ngd-relink-handle-halo-color-hover // Halo color on hover --ngd-relink-handle-halo-color-drag // Halo color while the end is draggedYou can also override them per edge with the matching --edge-relink-handle-* variables, in the same way as --edge-stroke. Each state has its own variable, so set the hover and drag colors together with the default ones:
ng-diagram-base-edge.my-edge { --edge-relink-handle-size: 0.875rem; --edge-relink-handle-fill: #fff; --edge-relink-handle-fill-hover: #fff; --edge-relink-handle-fill-drag: #3b82f6; --edge-relink-handle-stroke: #3b82f6; --edge-relink-handle-stroke-hover: #3b82f6; --edge-relink-handle-stroke-drag: #3b82f6; --edge-relink-handle-halo-color-hover: rgba(59, 130, 246, 0.4); --edge-relink-handle-halo-color-drag: rgba(59, 130, 246, 0.4);}Each handle has a larger invisible hit area around it. It is 24 screen pixels wide. Its size is adjusted for zoom, so the handles stay easy to grab at any zoom level without growing visually. Hovering over the hit area shows the halo, the same as hovering over the handle itself.