Standalone Component#622
Conversation
… `layoutTransitionEffect`, and `applyVisualContinuityBeforeLayout` inputs configure rich continuity when a prior graph had nodes, allowing for animations between graph updates. - Breaking: `useLayoutTransitions` (default `true` controls `layout-js-driven`; when `false` it applies only during JS `mode: 'tween'`); - Enhancement: `GraphComponent` `edgePathSampleCount` input (default 48, clamped 2–512) for edge resampling in layout, morph, and `redrawEdge` - Enhancement: `transitionAfterChanges.morphCapture` for prior translate source (model vs main-chart DOM, optional model fallback) and `syncTargetsFromPositionAfterTick` / `snapAddedNodeIds`; `mergeGraphLayoutTransition` merges `morphCapture` with defaults - Enhancement: `drawComplete` emits after a completed draw/tick pass (paths bound, graph ready). Use to hide loading UI or run one-shot center/zoomToFit without flashing before first layout. - Enhancement: Minimap can be displayed on bottom. - Fix: Observable layouts (Cola, D3 force) faster than `afterNextRender`—superseded passes repaint link paths from the current model without full `redrawLines` so layout morph is not cancelled. - Fix: Layout morph keeps `oldLine` / `oldTextPath` on paths that are not resampled-tweening until the tween ends instead of snapping to the new route early. - Fix: Drag `updateEdge` for Dagre and DagreCluster uses orientation-aware paths aligned with DagreNodesOnly. - Chore: Update Storybook with documentation, examples
| /** Merged layout transition config from {@link transitionAfterChanges} and defaults. */ | ||
| get effectiveLayoutTransition(): GraphLayoutTransition { | ||
| return mergeGraphLayoutTransition(this.transitionAfterChanges()); | ||
| } |
There was a problem hiding this comment.
Getter recomputes merged config on every access
Medium Severity
effectiveLayoutTransition is a getter that calls mergeGraphLayoutTransition (which allocates new objects via spread operators and Object.fromEntries) on every access. It's transitively invoked via layoutMorphActive and layoutJsMorphEnabled in template @for loops for every node and link (e.g., [class.old-node], [ngTemplateOutletContext]), and called repeatedly per edge inside pushTickEdgeLink and redrawLines. For large graphs this creates hundreds of unnecessary intermediate objects per tick and change-detection cycle.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 5501254. Configure here.
| ></ng-container> | ||
| } | ||
| @if (!linkTemplate()) { | ||
| <svg:path class="edge line" /> |
There was a problem hiding this comment.
Default link path has no initial d attribute
Medium Severity
The default link template <svg:path class="edge line" /> no longer binds [attr.d]="link.line" (the old template had [attr.d]="link.line"). The d attribute is now set imperatively by D3 in redrawLines, which runs after afterNextRender. This leaves default-template edge paths without geometry for at least one rendered frame, causing a visible flash where edges are invisible before D3 paints them.
Reviewed by Cursor Bugbot for commit 5501254. Configure here.
- fix: blocker for drawing minimap in some instances - fix: restyle minimap colors - fix: drawComplete should fire when all nodes are ready
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
There are 5 total unresolved issues (including 3 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6d503b2. Configure here.
| return; | ||
| } | ||
| this.panTo(null, Number(y)); | ||
| }); |
There was a problem hiding this comment.
Effect-driven pan calls always bail out due to null guard
High Severity
The panOffsetXInput effect calls this.panTo(Number(x), null) and the panOffsetYInput effect calls this.panTo(null, Number(y)). However, panTo has a guard that returns immediately if either x or y is null. This means the imperative panOffsetX and panOffsetY signal inputs never actually pan the viewport — the old setter-based approach updated one axis at a time, but the new panTo method rejects any call where either argument is null.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 6d503b2. Configure here.


What kind of change does this PR introduce? (check one with "x")
What is the new behavior?
GraphComponentis now a standalone component built on signal APIs (input,output,model,contentChild,viewChildren) instead of decorator-based@Input/@Outputand classic queries. The publishedNgxGraphModuleandGraphModulewrappers are still there so existing NgModule-based applications can import the graph as before, but those modules are deprecated; new code should importGraphComponent(and add it to the consuming component or routeimportsarray) the same way you would any other standalone piece.graph.nodes()rather thangraph.nodes. Thelayout,curve, andactiveEntriesbindings usemodel()where the graph needs to write back internally; consumers generally keep using normal property bindings, while code inside the component updates via.set().OutputRefvalues, notEventEmitter, so they do not offer.pipe(). To keep using RxJS operators, turn an output into an observable withoutputToObservablefrom@angular/core/rxjs-interop(in an appropriate injection context), or subscribe to the ref directly. Payload types (such asNgxGraphStateChangeEvent) are unchanged; only the subscription shape differs.@ViewChildren/@ContentChildstyle access is replaced with signal query functions, so in component code you callgraph.linkElements()(or the other query methods) instead of keeping aQueryListon a property.animations: [ trigger(...) ]definition was removed. Enter styling on the root now uses the framework’sanimate.enterclass binding together with keyframes in the graph stylesheet, which replaces the old opacity:entertransition and avoids the deprecatedtriggerAPI. Angular does not support mixing the legacyanimationsmetadata andanimate.enter/animate.leaveon the same component. The old[@.disabled]host binding for the legacy tree is also gone; it was scoped to the previous animation system and did not drive node positions, but projected templates that relied on that subtree being disabled for their own[@...]triggers may need a different approach.zoomLevel,panOffsetX, andpanOffsetYare now signal inputs wired througheffect()tozoomToandpanTo, so the graph no longer assigns those via a property setter, and the effects run when the parent binding’s value actually changes. If a parent expression changes on every data refresh, you may see extra viewport updates; binding stable values (or leaving these inputs unset) matches the old mental model. Automatic fit and centering still come from the documentedautoZoom,autoCenter, andzoomToFit$behavior. If you usetransitionAfterChangeswith full-scope layout morph, whether the post-tick block runszoomToFitorcentercan differ from the additive or instant paths, as before.transitionAfterChanges,transitionDuringTransform,layoutTransitionEffect, andapplyVisualContinuityBeforeLayoutinputs configure rich continuity when a prior graph had nodes, allowing for animations between graph updates.useLayoutTransitions(defaulttruecontrolslayout-js-driven; whenfalseit applies only during JSmode: 'tween');GraphComponentedgePathSampleCountinput (default 48, clamped 2–512) for edge resampling in layout, morph, andredrawEdgetransitionAfterChanges.morphCapturefor prior translate source (model vs main-chart DOM, optional model fallback) andsyncTargetsFromPositionAfterTick/snapAddedNodeIds;mergeGraphLayoutTransitionmergesmorphCapturewith defaultsdrawCompleteemits after a completed draw/tick pass (paths bound, graph ready). Use to hide loading UI or run one-shot center/zoomToFit without flashing before first layout.afterNextRender—superseded passes repaint link paths from the current model without fullredrawLinesso layout morph is not cancelled.oldLine/oldTextPathon paths that are not resampled-tweening until the tween ends instead of snapping to the new route early.updateEdgefor Dagre and DagreCluster uses orientation-aware paths aligned with DagreNodesOnly.Does this PR introduce a breaking change? (check one with "x")
Note
High Risk
High risk because it introduces breaking public API changes (standalone component, signal-based inputs/outputs/queries) and rewrites core rendering/animation logic for node/edge layout transitions and viewport transforms.
Overview
Migrates
ngx-graphto Angular 20+ standalone + signals.GraphComponentswitches from decorator-based@Input/@Output+EventEmitter+@ViewChildren/@ContentChildto signal APIs (input/model/output,viewChildren/contentChild), andGraphModule/NgxGraphModuleare retained but deprecated and updated to import standalone artifacts.Reworks rendering + transitions. Removes legacy
@angular/animationstriggers in favor ofanimate.enter+ CSS keyframes, adds JS-driven layout morphing via new transition inputs (transitionAfterChanges,useLayoutTransitions,edgePathSampleCount, viewport pan easing, optional layout “flair” effect), and adds adrawCompleteoutput emitted once paths are bound and dims are ready.Improves edge/minimap behavior and adds new utilities/tests. Introduces shared edge-geometry helpers for Dagre/ELK drag routing, extends minimap positioning (upper/lower corners) with margins and corrected pan-to math, adjusts minimap styling, and adds extensive unit tests covering transition merging, drag edge geometry, redraw/resampling, and
drawComplete.Reviewed by Cursor Bugbot for commit 6d503b2. Bugbot is set up for automated code reviews on this repo. Configure here.