Skip to content

Standalone Component#622

Open
steveblue wants to merge 3 commits intoangular-upgrade-21from
ngx-graph-major-changes-21
Open

Standalone Component#622
steveblue wants to merge 3 commits intoangular-upgrade-21from
ngx-graph-major-changes-21

Conversation

@steveblue
Copy link
Copy Markdown
Contributor

@steveblue steveblue commented Apr 22, 2026

What kind of change does this PR introduce? (check one with "x")

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Other... Please describe:

What is the new behavior?

  • Breaking: GraphComponent is now a standalone component built on signal APIs (input, output, model, contentChild, viewChildren) instead of decorator-based @Input / @Output and classic queries. The published NgxGraphModule and GraphModule wrappers are still there so existing NgModule-based applications can import the graph as before, but those modules are deprecated; new code should import GraphComponent (and add it to the consuming component or route imports array) the same way you would any other standalone piece.
  • Breaking: From TypeScript, inputs behave like signal readers: for example, read the current node list with graph.nodes() rather than graph.nodes. The layout, curve, and activeEntries bindings use model() where the graph needs to write back internally; consumers generally keep using normal property bindings, while code inside the component updates via .set().
  • Breaking: Outputs are OutputRef values, not EventEmitter, so they do not offer .pipe(). To keep using RxJS operators, turn an output into an observable with outputToObservable from @angular/core/rxjs-interop (in an appropriate injection context), or subscribe to the ref directly. Payload types (such as NgxGraphStateChangeEvent) are unchanged; only the subscription shape differs.
  • Breaking: @ViewChildren / @ContentChild style access is replaced with signal query functions, so in component code you call graph.linkElements() (or the other query methods) instead of keeping a QueryList on a property.
  • Breaking: The in-component animations: [ trigger(...) ] definition was removed. Enter styling on the root now uses the framework’s animate.enter class binding together with keyframes in the graph stylesheet, which replaces the old opacity :enter transition and avoids the deprecated trigger API. Angular does not support mixing the legacy animations metadata and animate.enter / animate.leave on 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.
  • Breaking: Optional viewport inputs zoomLevel, panOffsetX, and panOffsetY are now signal inputs wired through effect() to zoomTo and panTo, 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 documented autoZoom, autoCenter, and zoomToFit$ behavior. If you use transitionAfterChanges with full-scope layout morph, whether the post-tick block runs zoomToFit or center can differ from the additive or instant paths, as before.
  • Enhancement: transitionAfterChanges, transitionDuringTransform, 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

Does this PR introduce a breaking change? (check one with "x")

  • Yes
  • No

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-graph to Angular 20+ standalone + signals. GraphComponent switches from decorator-based @Input/@Output + EventEmitter + @ViewChildren/@ContentChild to signal APIs (input/model/output, viewChildren/contentChild), and GraphModule/NgxGraphModule are retained but deprecated and updated to import standalone artifacts.

Reworks rendering + transitions. Removes legacy @angular/animations triggers in favor of animate.enter + CSS keyframes, adds JS-driven layout morphing via new transition inputs (transitionAfterChanges, useLayoutTransitions, edgePathSampleCount, viewport pan easing, optional layout “flair” effect), and adds a drawComplete output 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.

… `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());
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5501254. Configure here.

Comment thread projects/swimlane/ngx-graph/src/lib/graph/layouts/edge-geometry.ts
></ng-container>
}
@if (!linkTemplate()) {
<svg:path class="edge line" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

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
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 5 total unresolved issues (including 3 from previous reviews).

Fix All in Cursor

❌ 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));
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6d503b2. Configure here.

Comment thread projects/swimlane/ngx-graph/src/lib/graph/graph.component.ts
@steveblue steveblue requested a review from jogaj April 23, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants