✨ Added
NgDiagramDefaultEdgeLabelComponentis now public — the component rendering the default edge label chip (ng-diagram-default-edge-label) is exported from the package, so custom edge templates can compose it insideng-diagram-base-edge-labeland get the default label look (theme-aware chip, hover and selected border highlights) without copying its styles. The hover and selected border highlights now work in any edge template, and the new--edge-label-border-color-hoverand--edge-label-border-color-selectedvariables override their colors (each falling back to--edge-label-border-color, then the matching--ngd-default-edge-stroke-hover/-selectedtoken). (#777)- Configurable resize sides —
ng-diagram-node-resize-adornmentaccepts anactiveSidesinput that limits which sides of the node can be grabbed, for example[activeSides]="['right', 'bottom']"for a node anchored at its top-left corner. All four lines keep rendering (they double as the selection frame), but the ones left out are inert and show no resize cursor, and a corner handle renders only when both of its sides are listed. Omitting the input keeps all four sides and corners active, so existing templates are unaffected. The newSidetype names one of the four sides of a rectangular area;PortSideis now an alias of it (#775) - Remove ports from default nodes – this is now possible to remove ports from default nodes (#759)
waitForMeasurementsoption on service methods —addNodes,addEdges,updateNode,updateNodes,updateNodeData,updateEdge,updateEdges,updateEdgeDataonNgDiagramModelService,resizeNodeonNgDiagramNodeServiceandpasteonNgDiagramClipboardServiceacceptoptions?: { waitForMeasurements?: boolean }; when set, the returned promise resolves only after the elements affected by the change have been measured — useful whenever the next step depends on real dimensions (for examplezoomToFit()orcenterOnNode()). The option exists only on methods whose changes can trigger measurements; deletions and other model-only operations have nothing to measure, so awaiting the method itself is already enough there. Inside an already active transaction the option is ignored with a console warning — pass{ waitForMeasurements: true }to the transaction itself instead (#769)- Customizable runtime-property stripping –
initializeModelandinitializeModelAdapteraccept an optionalInitializeModelOptionsparameter to control which properties are stripped on initialization andtoJSON(). Overriding the defaults can break the diagram — use at your own risk (#760) - Resize snap offset — new
computeSnapOffsetForNodeSizeanddefaultResizeSnapOffsetoptions onSnappingConfig. Snapped node sizes now follow the sequenceoffset + n * snapper axis, so a node with a 60px header and a 50px vertical resize snap can snap to 60, 110, 160, … instead of 50, 100, 150, …. Defaults to{ width: 0, height: 0 }(#765, #770) — thanks @logan-brd for the suggestion! 🙏 NgDiagramService.transactionalways returns the commit promise — the synchronous-callback overload used to returnvoid, and for some async callbacks the commit promise was silently discarded; all overloads now returnPromise<TransactionResult>, so awaiting a transaction reliably waits for its commit (#769)- Cancel in-progress gestures – new
NgDiagramService.cancelActiveInteraction()aborts the active linking, drag, resize, rotate or pan gesture immediately and restores the pre-gesture state: dragged nodes snap back to their initial positions, resized/rotated nodes regain their original geometry, and the temporary edge is discarded (state cleared, document listeners removed, no need to wait for pointer release). Bound to Escape by default via the newcancelInteractionshortcut action. TheedgeDrawEndedevent gains acancelledreason, andnodeDragEnded/nodeResizeEnded/nodeRotateEndedgain an optionalcancelReasonfield (#747, #766)
🧩 Changed
NgDiagramService.invalidateMeasurementsis awaitable — it returns aPromise<void>that resolves once the triggered re-measurements have settled, so the invalidated elements'size,measuredPortsandmeasuredLabelsread fresh on the next line. An element that delivers no new measurement (unmounted, zero-size, or unchanged) does not stall the promise. Existing call sites keep working (void→Promise<void>); ignoring the promise is fine (#778)- Awaitable service methods — every mutating method on the public services (
NgDiagramModelService,NgDiagramNodeService,NgDiagramSelectionService,NgDiagramClipboardService,NgDiagramGroupsService,NgDiagramViewportService) now returns aPromise<void>that resolves once the change has been applied to the model, so the next line of code reads the updated state. Read it throughgetModel()or the getter methods (getNodeById,getEdgeById,getConnectedEdges, …) — these are synchronous with the model. Thenodes(),edges()andmetadata()signals refresh with Angular's change detection, so right after theawait(and inside diagram event handlers) they can still show the previous state. Existing call sites keep working (void→Promise<void>). Do not await these promises from inside a middleware — the update pipeline is not re-entrant and the await would deadlock; fire-and-forget calls are safe there (#769)
🐛 Fixed
- Gesture reliability — drag, resize, rotate and linking now behave correctly with slow (asynchronous) middlewares and throwing user callbacks:
nodeDragStarted/nodeDragEnded,nodeResizeStarted/nodeResizeEndedandnodeRotateStarted/nodeRotateEndedalways come in pairs and report the node(s) of their own gesture; a throwing user callback (e.g. connection validation or a grouping check) no longer leaves a gesture stuck — linking cannot become permanently blocked and node measurements are not suppressed afterwards; and deleting a node mid-gesture (while resizing, rotating or linking it) cleans up correctly (#769) - A throwing middleware no longer freezes the diagram — an uncaught error (or unhandled rejection) inside a middleware used to silently freeze every subsequent update; it now rejects the promise returned by the mutating call and the diagram keeps working (#769)
waitForMeasurementsno longer resolves early when measurement rounds overlap — a leftover debounce timer from a previous round could settle a newly started round before its measurements arrived (#778)- Initialization resilience — an error thrown during initialization (e.g. by a custom
ModelAdapter) no longer leaves the diagram in a state where nothing gets measured; initialization completes and the error is logged (#769) waitForMeasurementsaccuracy — a concurrent unrelated update can no longer makewaitForMeasurementsresolve too early or wait for the wrong elements; the option passed to a nested transaction now logs a warning instead of silently resolving before the updates are applied. As a consequence, a transaction now waits only for measurements caused by its own changes: a transaction with an empty callback resolves immediately, so it no longer works as a barrier that waits for unrelated measurement activity (a pattern some apps used afterinvalidateMeasurements) —await invalidateMeasurements()itself instead (#769)- Overlapping transactions no longer lose updates — a transaction (or a plain service call) issued while a previous un-awaited transaction was still committing used to be silently dropped; every transaction now applies its own updates, and the overlap logs a console warning recommending to await transactions (#769)
- Port and edge-label measurements no longer get lost during rapid re-rendering — fast mount/unmount cycles (e.g. virtualization while panning) could apply port and label updates out of order or drop them entirely when one update failed, leaving stale port positions or labels; a failed batch no longer blocks all future ones either (#769)
NgDiagramNodeService.resizeNodenow resizes an unselected group — a programmaticresizeNodecall targeting a group node used to silently do nothing unless the group was selected; it now applies regardless of selection state, still enforcing the group resize constraints (children containment, minimum node size and resize snapping). Interactive resize behavior is unchanged. Calls that used to silently no-op now apply (#772)- Dangling edges survive persistence —
initializeModelandinitializeModelAdapterno longer strip the authoredsourcePosition/targetPositionof an edge's free endpoint (emptysource/target), andtoJSON()now includes them in the serialized output, so dangling edges load from a persisted model the same way they work when added at runtime; no more "Invalid edge coordinates detected" for valid dangling edges on init (#751, #760) - Port
side/typeno longer stay stale after a port moves — recreating a port with the same id in a different place (e.g. toggling a port between aside: 'left'and aside: 'right'block) now updatesmeasuredPortswith the newside/type, so edges anchor to the correct side; measuredsize/positionkeep coming from the DOM as before. The same applies to edge labels re-registered with a changedpositionOnEdge(#750, #763) - Group with children jumping on resize snap — resizing a group that contains child nodes from the bottom/right edge no longer moves the group when a resize snap is configured (#765, #770) — thanks @logan-brd for the issue submission! 🙏
- Keyboard shortcuts work when a gesture starts with focus outside the diagram — the resize/rotate handles stop the pointerdown propagation, which used to skip the diagram's focus grab; starting a resize right after clicking an external control (e.g. a toolbar button) left every shortcut dead — in particular Escape could not cancel the gesture. The diagram now takes focus on any pointerdown inside it (#766)
- Touch gestures stay exclusive under virtualization — on touch devices with virtualization enabled, nodes and ports leaving the rendered area during a pan or pinch-zoom no longer reset the internal gesture-exclusivity state, so a stray touch can no longer start a second gesture (drag, resize, linking) in the middle of an active one (#766)
- Resize snapping no longer cuts group children — with
allowResizeBelowChildrenBounds: false, a snapped group size that would land inside the children bounds now rounds up to the next snap value that still contains the children (#770) - Size changes made right after a resize gesture are no longer overwritten by a stale measurement — a middleware reverting an invalid resize on
resizeNodeStop, or anodeResizeEndedhandler correcting the size, used to lose when the mouse button was released while still moving; the corrected size now always wins. The resized node is also re-measured once after the gesture, so when CSS (e.g.min-width) keeps the element larger than the resized size, the model picks up the element's real size (#771, #776) — thanks @logan-brd for the report and the reproduction! 🙏 - Releasing the mouse while still moving no longer loses the end of a resize, drag or rotation — the node used to stop short of where the pointer was released (the faster the release, the bigger the miss; with resize snapping the difference could reach a whole grid step). The release position is now applied as the final update of the gesture, before
nodeDragEnded/nodeResizeEnded/nodeRotateEndedfires, so the reported geometry always matches where the pointer stopped (#771, #779) - Drawing an edge is cancelled when another touch gesture takes over — on touch devices, a two-finger pan or pinch started in the middle of drawing an edge used to finish the edge at the other finger's position, sometimes connecting it to a target the user never pointed at; the drawing is now cancelled and
edgeDrawEndedreports thecancelledreason (#779)