⬆️ Upgrade notes
Most apps can upgrade without changes. Check these points first:
hiddenis now a library property on nodes and edges. If your app already storeshidden: trueon a node or an edge, that element is hidden after the upgrade.- A
tabindexattribute on<ng-diagram>no longer takes effect. The diagram controls this attribute now. Use[tabbable]="false"to remove the diagram from the Tab order. EdgeDrawEndedEvent.sourceis optional. WithstrictNullChecks, code that readsevent.sourcewithout a check does not compile. Useevent.source?.id.SelectionRemovedEventhas a new required fielddetachedEdges. This affects only code that builds this event object by hand.ModelActionTypehas new values ('startRelinking','finishRelinking','startLinkingFromPosition','templateVisibilityChange'). Middlewares with an exhaustiveswitchover the action type need cases for them.
✨ Added
- Dangling edges — edges with one or both ends not connected to a node. A free end has
source/targetset to the empty string''(nevernullorundefined), nosourcePort/targetPort(undefined), and its position insourcePosition/targetPosition. Opt in with the newdanglingEdges.enabledconfig: a draw dropped on empty canvas then keeps the edge (shouldKeepOnDropdecides per edge) and a relink drop detaches the endpoint. Dangling edges route between their anchors, are saved and restored with the model, copy and paste together with their node, and box selection picks them by their anchors. New utilitiesisDanglingEdge,hasFreeEndpoint,getDanglingEndpoints,getNearestDanglingEndpointInRangeandcomputeDetachAnchor; newNgDiagramModelService.detachEdge/attachEdge(attach validates throughlinking.validateConnectionwithreason: 'attach'); new CSS hooksng-diagram-edge--danglingon the edge wrapper anddanglingonng-diagram-base-edgeand its path (#810) - Keep edges when deleting a node — with
danglingEdges.detachOnNodeDelete, deleting a node keeps its edges as dangling edges anchored where their port was, andselectionRemoved.detachedEdgeslists them.shouldDetachOnNodeDeletedecides per endpoint. An edge is still deleted when it is deleted explicitly, when it is hidden only because of the node it loses (an edge that stays hidden on its own is detached like any other), or when it loses both ends at once (unless the callback keeps both) (#810) - Edge relinking — reconnect an edge by dragging one of its ends. Set
linking.defaultRelinkabletotrueand a selected edge shows a handle at each end; set it to'source'or'target'to allow only that end. An edge overrides the default with its ownrelinkableproperty, so you can lock single edges or open only some;isEdgeEndRelinkable(edge, end, defaultRelinkable)resolves the two for custom templates. Dragging a handle previews the new connection and commits it on drop. The drop is validated bylinking.validateConnectionwithreason: 'relink'. A drop on empty canvas detaches the end whendanglingEdges.enabledallows it; an invalid drop, a drop back on the same port and Escape leave the edge unchanged. NewedgeRelinkStarted/edgeRelinkEndedevents report the gesture and its result (noTarget,invalidConnectionorcancelledon failure). The handles show a halo on hover and stay visible while an end is dragged. Style them with the--ngd-relink-handle-*theme variables (size, stroke width, halo size, and colors for the default, hover and drag states) or per edge with--edge-relink-handle-*(#630, #810, #814) — thanks @axhar-ibraheem for the suggestion! 🙏 NgDiagramService.startLinkingFromPosition(position)— starts drawing an edge from a point on the canvas (flow coordinates) instead of from a node; the free end follows the pointer until a click connects it to a port or leaves a dual dangling edge on empty canvas. RequiresdanglingEdges.enabled(ignored with a console warning otherwise) (#810)hiddenflag on nodes and edges — the new optionalhiddenproperty onSimpleNodeandEdgehides an element without removing it from the model. Hidden elements stay in the DOM withdisplay: none, keep their size and position, never block initialization orwaitForMeasurements, and are measured again automatically when they become visible. Hiding a group hides all of its descendants, and an edge is hidden when either of its endpoint nodes is hidden. The resulting effective visibility is available as the read-onlycomputedHiddenproperty. Visible nodes, edges and labels never get an inlinedisplaystyle, so existing CSS-based workarounds keep working. Ports manage their own inlinedisplayas before: hide them with the port'shiddeninput, not with CSS. Upgrade note:hiddenis now a library property on nodes and edges. If your app already storeshidden: trueon nodes or edges, those elements become hidden after the upgrade (#799)- Hidden elements are ignored by user interactions — effectively hidden nodes and edges cannot be clicked, and they are left out of
selectAll, keyboard move, drag, box selection, linking (starting a link from a hidden node does nothing and logs a console warning),zoomToFitandcomputePartsBoundsbounds, virtualization, edge routing, and the resize limits of their group. A collapsed group can be resized freely, and its hidden children reappear at their old positions on expand. The minimap follows the canvas: hidden nodes are not drawn (neither as default rectangles nor as custom minimap templates) and do not enlarge the minimap bounds, in both rendering modes. Programmatic APIs (select,centerOnNode, z-order and group commands) intentionally do not skip hidden elements. Hiding does not deselect: a hidden element keeps itsselectedflag but cannot be moved, anddeleteSelectionskips hidden selected elements. Descendants of a deleted visible group and edges of deleted nodes are still removed (#799) hiddeninputs andngDiagramHiddendirective in templates —ng-diagram-portandng-diagram-base-edge-labelaccept ahiddeninput that hides a port or label independently of its node or edge (for example, ports visible only in an edit mode). A hidden port cannot receive a link, and the linking preview does not snap to it. Edges connected to a hidden port stay visible and keep using its last measured position. The newNgDiagramHiddenDirective([ngDiagramHidden]) hides a node or edge from inside its template. The model flag, the template binding and a hidden parent all feed the same effective visibility: any of them hides the element. Bindings that change in the same change-detection cycle are processed together in one pass, but hiding many elements through the modelhiddenflag is still the cheapest option. The directive does not work with virtualization, because hiding the element would remove the template that holds the binding. In that case the binding is ignored and a console warning is logged. Use the modelhiddenflag instead (#799)tabbableinput on<ng-diagram>— set[tabbable]="false"to remove the diagram's own Tab stops (the container element and the watermark link) from the page's Tab order, for example when your application manages Tab order itself with a roving tabindex on the nodes. Both elements stay clickable and keep working with screen readers, and keyboard shortcuts keep working once the diagram is focused, for example by a click. Focusable content in your own node and edge templates keeps its own Tab stops. Defaults totrue. If you set atabindexattribute on<ng-diagram>in your template, it no longer takes effect — the diagram now controls that attribute; use[tabbable]="false"instead (#790, #795) — thanks @jimmeryn for the issue submission! 🙏
🧩 Changed
EdgeDrawEndedEvent.sourceis now optional — a draw can start from a position instead of a node (startLinkingFromPosition). Read it asevent.source?.id. Breaking only for apps compiled withstrictNullChecksthat readevent.sourcewithout a check (#810)edgeDrawEndedreports success for a canvas drop kept as a dangling edge — withdanglingEdges.enabled, such a drop fires withsuccess: trueand the creatededge, and withouttarget/targetPort(before, every canvas drop cancelled withnoTarget). If your app created its own edge onreason === 'noTarget', remove that code before enabling the flag, or edges are created twice. The deprecatededgeDrawnnever fires for kept dangling edges (#810)linking.validateConnectionreceives an optional fifth argument — aConnectionValidationContextwithreason: 'draw' | 'relink' | 'attach'and, for relink and attach, theedgeandendbeing reconnected, so one validator can treat the three cases differently. Thesourcenode argument can now benull: for draws started withstartLinkingFromPositionand when the other end of a relinked edge is free (#810)SelectionRemovedEventhas a new requireddetachedEdgesfield — the edges kept as dangling edges instead of deleted (danglingEdges.detachOnNodeDelete),[]when none. Breaking only for code that builds this event object by hand (#810)ModelActionTypegained'startRelinking','finishRelinking'and'startLinkingFromPosition'— breaking for middlewares with exhaustive switches over the action type (#810)- Box selection counts dangling edges by their free-endpoint anchors — a box drawn around a free endpoint's
sourcePosition/targetPositionselects the edge (#810) - Paste moves free endpoints — pasting moves the free endpoints of dangling edges together with the rest of the content (for manual-routing edges also their
points), and paste at a position centers on nodes and free endpoints alike (#810) - Z-index recomputes when an edge's
source/targetchanges programmatically — afterupdateEdgeorattachEdgechanges an endpoint, the edge is layered relative to its new nodes (#810) ng-diagram-palette-item-previewtakes no space in the layout — the preview renders nothing on the page, whatever content it holds and however the palette is laid out. It is a block, so inline content placed next to it inside a palette item now starts on a new line (#801)NgDiagramPaletteItemPreviewComponent.previewis for reading content and size — the element behind this signal is not rendered in the page flow. Read the preview's natural size or content from it; its position is meaningless (#801)- The drag image zoom is capped at 3x — at higher zoom levels the image under the cursor stays at 3x, so starting a drag stays smooth; the dropped node still lands at the current zoom (#801)
- Copying a group now includes its content —
copyandcutalso take all descendants of the copied nodes and the edges between the copied nodes. Pasting a group now recreates its children and their edges (before, a pasted group was empty). Cutting a collapsed group keeps its hidden children: they are pasted back together with the group. Selected elements that are effectively hidden are skipped bycopy, the same as indeleteSelection. Pasted hidden content is not selected, so a paste never creates a selection that you cannot see. The paste position at the cursor is computed from the visible pasted content only (#799)
🐛 Fixed
- Ports are measured when
<ng-diagram>is rendered outside the component that declaresprovideNgDiagram()— when the diagram was outside the DOM of that component (for example withngTemplateOutlet, a portal or an overlay), changing a node's size or rotation loggedNode measurement failed: Node element not foundand left the ports at their old positions. Port positions now update in this layout too (#811, #815) — thanks @montella1507 for the issue submission! 🙏 - The cursor no longer flickers while an edge is drawn or relinked — the default edge and edge labels do not react to the pointer while they preview a connection, and the diagram shows the grabbing hand over the canvas for the whole gesture. The preview's edge wrapper carries an
ng-diagram-edge--temporaryclass; use it to keep interactive content in your own edge template out of the pointer's way during a draw (#812) - A draw always ends cleanly — when the system takes the pointer away, for example a palm-rejected touch or a system gesture, the edge being drawn is cancelled instead of leaving the diagram in drawing mode. A diagram destroyed in the middle of a draw removes its listeners (#812)
- Arrow keys pan the viewport when the selection cannot move — with
nodeDraggingEnabled: falseand a node selected, the arrow keys used to do nothing. The same happened when onlydraggable: falsenodes were selected. Now the arrow keys pan the viewport whenever the selection cannot move. A selection that can move still moves as before (#809) zoomToFit({ nodeIds })fits only the requested nodes — withoutedgeIds, the fit used to include every edge of the diagram. Fitting a few nodes in a connected diagram therefore zoomed out to the whole diagram, and fitting only hidden nodes showed the whole diagram instead of doing nothing. Now only edges with both ends on the requested nodes are included. PassingedgeIdsworks as before (#808)- Pasted manual-routing edges keep their shape at the new location — an edge with
routingMode: 'manual'pasted together with its nodes used to be drawn at the original location; itspointsnow move with the pasted nodes (#810) - Pasting an edge without its nodes no longer duplicates the original connection — an edge copied without one or both of its endpoint nodes used to be pasted between the original nodes. Every paste added one more identical connection. Now an end whose node was not copied is pasted as a free end. It moves with the rest of the pasted content, and you can move it or reconnect it. Cutting a single edge and pasting it therefore gives a free edge, not a copy of the original connection. Edges copied together with their nodes are still pasted between the new nodes (#807)
deleteNodeson a group deletes its whole subtree —NgDiagramModelService.deleteNodesnow also deletes all descendants of the deleted nodes and their edges, the same asdeleteSelection. Before, the children stayed in the model with agroupIdthat pointed at a deleted node, and the children of a hidden group became visible again. To delete a group but keep its children, callNgDiagramGroupsService.removeFromGroupfirst (#806)- Palette preview no longer bleeds over the palette or stretches the page — at high zoom levels
ng-diagram-palette-item-previewno longer paints over the palette items and no longer adds scrollbars to the page, whatever the layout around the palette (#801) - Palette drag image matches the preview it shows — the image under the cursor while dragging a palette item is now exactly the scaled preview, at every zoom level and in every browser. The preview can render any element, of any size, and the drag image will match it (#801)
- Zero-size measurements no longer break geometry — when a rendered element was hidden with CSS
display: none, the browser reported a size of 0×0, and this value overwrote the node'ssizeand the measured geometry of its ports and labels in the model. Measurements of 0×0 are now ignored (a measurement like 200×0 is still applied). Invalid measurements collected during initialization are no longer written over existing geometry either (#799) computePartsBoundsno longer pulls the result to the origin — when one of the lists was empty (most oftencomputePartsBounds(nodes, [])) or had nothing measured in it, the result was extended to include the point(0, 0). The bounds now cover exactly the measured parts. When there is nothing to measure at all, a zero-size rectangle at the origin is returned, as before. The minimap now frames diagrams placed away from the origin correctly, andzoomToFit({ nodeIds })fits the requested nodes instead of the origin.zoomToFiton an empty or unmeasured diagram does nothing (#793)
⚠️ Deprecated
NgDiagramPaletteItemPreviewComponent.scaleTransform— the drag image is scaled internally, so this value is no longer used. It will be removed in the next major version (#801)NgDiagramPaletteItemPreviewComponentprotected membersisChrome,isSafari,isVisibleandscale— no longer used by the preview. They keep working for subclasses and will be removed in the next major version (#801)