Skip to content

Commit 5501254

Browse files
committed
feat: upgrade component to latest
1 parent 4356e31 commit 5501254

9 files changed

Lines changed: 318 additions & 280 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
## HEAD (unreleased)
44

5+
- 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.
6+
- 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()`.
7+
- 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.
8+
- 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.
9+
- 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.
10+
- 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.
511
- Enhancement: `transitionAfterChanges`, `transitionDuringTransform`, `layoutTransitionEffect`, and `applyVisualContinuityBeforeLayout` inputs configure rich continuity when a prior graph had nodes, allowing for animations between graph updates.
612
- Breaking: `useLayoutTransitions` (default `true` controls `layout-js-driven`; when `false` it applies only during JS `mode: 'tween'`);
713
- Enhancement: `GraphComponent` `edgePathSampleCount` input (default 48, clamped 2–512) for edge resampling in layout, morph, and `redrawEdge`

projects/swimlane/ngx-graph/src/lib/graph/graph.component.html

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
[style.transform]="layoutOuterTransform"
66
[style.transform-origin]="layoutEffectTransformOrigin"
77
[style.width.px]="width"
8-
[@animationState]="'active'"
9-
[@.disabled]="!animate"
8+
[animate.enter]="animate() ? 'ngx-graph-outer-fade' : null"
109
(mouseWheelUp)="onZoom($event, 'in')"
1110
(mouseWheelDown)="onZoom($event, 'out')"
1211
mouseWheel
@@ -20,8 +19,8 @@
2019
class="graph chart"
2120
>
2221
<defs>
23-
@if (defsTemplate) {
24-
<ng-container [ngTemplateOutlet]="defsTemplate"></ng-container>
22+
@if (defsTemplate()) {
23+
<ng-container [ngTemplateOutlet]="defsTemplate()"></ng-container>
2524
}
2625
@for (link of graph.edges; track link) {
2726
<svg:path class="text-path" [attr.id]="link.id"></svg:path>
@@ -40,18 +39,18 @@
4039
<svg:g
4140
#clusterElement
4241
class="node-group"
43-
[class.old-node]="(animate || layoutJsMorphEnabled) && oldClusters.has(node.id)"
42+
[class.old-node]="(animate() || layoutJsMorphEnabled) && oldClusters.has(node.id)"
4443
[id]="node.id"
4544
[attr.transform]="node.transform"
4645
(click)="onClick(node)"
4746
>
48-
@if (clusterTemplate && !node.hidden) {
47+
@if (clusterTemplate() && !node.hidden) {
4948
<ng-container
50-
[ngTemplateOutlet]="clusterTemplate"
49+
[ngTemplateOutlet]="clusterTemplate()"
5150
[ngTemplateOutletContext]="{ $implicit: node, transitionAfterChangesActive: layoutJsMorphEnabled }"
5251
></ng-container>
5352
}
54-
@if (!clusterTemplate && !node.hidden) {
53+
@if (!clusterTemplate() && !node.hidden) {
5554
<svg:g class="node cluster">
5655
<svg:rect
5756
[attr.width]="node.dimension.width"
@@ -71,19 +70,19 @@
7170
<svg:g
7271
#nodeElement
7372
class="node-group"
74-
[class.old-node]="(animate || layoutJsMorphEnabled) && oldCompoundNodes.has(node.id)"
73+
[class.old-node]="(animate() || layoutJsMorphEnabled) && oldCompoundNodes.has(node.id)"
7574
[id]="node.id"
7675
[attr.transform]="node.transform"
7776
(click)="onClick(node)"
7877
(mousedown)="onNodeMouseDown($event, node)"
7978
>
80-
@if (nodeTemplate && !node.hidden) {
79+
@if (nodeTemplate() && !node.hidden) {
8180
<ng-container
82-
[ngTemplateOutlet]="nodeTemplate"
81+
[ngTemplateOutlet]="nodeTemplate()"
8382
[ngTemplateOutletContext]="{ $implicit: node, transitionAfterChangesActive: layoutJsMorphEnabled }"
8483
></ng-container>
8584
}
86-
@if (!nodeTemplate && !node.hidden) {
85+
@if (!nodeTemplate() && !node.hidden) {
8786
<svg:g class="node compound-node">
8887
<svg:rect
8988
[attr.width]="node.dimension.width"
@@ -101,13 +100,13 @@
101100
<svg:g class="links">
102101
@for (link of graph.edges; track trackLinkBy($index, link)) {
103102
<svg:g #linkElement class="link-group" [id]="link.id">
104-
@if (linkTemplate) {
103+
@if (linkTemplate()) {
105104
<ng-container
106-
[ngTemplateOutlet]="linkTemplate"
105+
[ngTemplateOutlet]="linkTemplate()"
107106
[ngTemplateOutletContext]="{ $implicit: link, transitionAfterChangesActive: layoutJsMorphEnabled }"
108107
></ng-container>
109108
}
110-
@if (!linkTemplate) {
109+
@if (!linkTemplate()) {
111110
<svg:path class="edge line" />
112111
}
113112
</svg:g>
@@ -118,19 +117,19 @@
118117
<svg:g
119118
#nodeElement
120119
class="node-group"
121-
[class.old-node]="(animate || layoutJsMorphEnabled) && oldNodes.has(node.id)"
120+
[class.old-node]="(animate() || layoutJsMorphEnabled) && oldNodes.has(node.id)"
122121
[id]="node.id"
123122
[attr.transform]="node.transform"
124123
(click)="onClick(node)"
125124
(mousedown)="onNodeMouseDown($event, node)"
126125
>
127-
@if (nodeTemplate && !node.hidden) {
126+
@if (nodeTemplate() && !node.hidden) {
128127
<ng-container
129-
[ngTemplateOutlet]="nodeTemplate"
128+
[ngTemplateOutlet]="nodeTemplate()"
130129
[ngTemplateOutletContext]="{ $implicit: node, transitionAfterChangesActive: layoutJsMorphEnabled }"
131130
></ng-container>
132131
}
133-
@if (!nodeTemplate && !node.hidden) {
132+
@if (!nodeTemplate() && !node.hidden) {
134133
<svg:circle
135134
[attr.r]="defaultNodeCircleRadius(node)"
136135
[attr.cx]="node.dimension.width / 2"
@@ -151,7 +150,7 @@
151150
></svg:rect>
152151
</svg:clipPath>
153152

154-
@if (showMiniMap) {
153+
@if (showMiniMap()) {
155154
<svg:g class="minimap" [attr.transform]="minimapTransform" [attr.clip-path]="'url(#' + minimapClipPathId + ')'">
156155
<svg:rect
157156
class="minimap-background"
@@ -173,23 +172,23 @@
173172
<svg:g
174173
#nodeElement
175174
class="node-group"
176-
[class.old-node]="(animate || layoutJsMorphEnabled) && oldNodes.has(node.id)"
175+
[class.old-node]="(animate() || layoutJsMorphEnabled) && oldNodes.has(node.id)"
177176
[id]="node.id"
178177
[attr.transform]="node.transform"
179178
>
180-
@if (miniMapNodeTemplate) {
179+
@if (miniMapNodeTemplate()) {
181180
<ng-container
182-
[ngTemplateOutlet]="miniMapNodeTemplate"
181+
[ngTemplateOutlet]="miniMapNodeTemplate()"
183182
[ngTemplateOutletContext]="{ $implicit: node, transitionAfterChangesActive: layoutJsMorphEnabled }"
184183
></ng-container>
185184
}
186-
@if (!miniMapNodeTemplate && nodeTemplate) {
185+
@if (!miniMapNodeTemplate() && nodeTemplate()) {
187186
<ng-container
188-
[ngTemplateOutlet]="nodeTemplate"
187+
[ngTemplateOutlet]="nodeTemplate()"
189188
[ngTemplateOutletContext]="{ $implicit: node, transitionAfterChangesActive: layoutJsMorphEnabled }"
190189
></ng-container>
191190
}
192-
@if (!nodeTemplate && !miniMapNodeTemplate && !node.hidden) {
191+
@if (!nodeTemplate() && !miniMapNodeTemplate() && !node.hidden) {
193192
<svg:circle
194193
[attr.r]="defaultNodeCircleRadius(node) / minimapScaleCoefficient"
195194
[attr.cx]="node.dimension.width / 2 / minimapScaleCoefficient"

projects/swimlane/ngx-graph/src/lib/graph/graph.component.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
/* Replaces legacy @angular/animations :enter (see animate.enter in graph.component.html). */
2+
.ngx-graph-outer-fade {
3+
animation: ngx-graph-outer-fade-in 500ms 100ms ease both;
4+
}
5+
6+
@keyframes ngx-graph-outer-fade-in {
7+
from {
8+
opacity: 0;
9+
}
10+
to {
11+
opacity: 1;
12+
}
13+
}
14+
115
.minimap {
216
.minimap-background {
317
fill: rgba(0, 0, 0, 0.1);

projects/swimlane/ngx-graph/src/lib/graph/graph.component.spec.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { Layout } from '../models/layout.model';
88
import { Edge } from '../models/edge.model';
99
import { ClusterNode, CompoundNode, Node } from '../models/node.model';
1010
import { GraphComponent } from './graph.component';
11-
import { GraphModule } from './graph.module';
1211

1312
/** Synchronous layout for tests: positions nodes/clusters/compound nodes and assigns edge polylines. */
1413
class TestSyncLayout implements Layout {
@@ -70,7 +69,7 @@ class TestLayoutWithCustomParseTranslate extends TestSyncLayout {
7069
(drawComplete)="onDrawComplete()"
7170
></ngx-graph>
7271
`,
73-
standalone: false
72+
imports: [GraphComponent]
7473
})
7574
class TestGraphDrawCompleteHostComponent {
7675
syncLayout = new TestSyncLayout();
@@ -93,8 +92,7 @@ class TestGraphDrawCompleteHostComponent {
9392
describe('GraphComponent drawComplete', () => {
9493
beforeEach(async () => {
9594
await TestBed.configureTestingModule({
96-
imports: [GraphModule],
97-
declarations: [TestGraphDrawCompleteHostComponent]
95+
imports: [TestGraphDrawCompleteHostComponent]
9896
}).compileComponents();
9997
});
10098

@@ -113,9 +111,9 @@ describe('GraphComponent drawComplete', () => {
113111
const graph = graphEl.componentInstance as GraphComponent;
114112

115113
expect(graph.graph.edges.length).toBe(1);
116-
expect(graph.linkElements?.length ?? 0).toBe(graph.graph.edges.length);
114+
expect(graph.linkElements()?.length ?? 0).toBe(graph.graph.edges.length);
117115

118-
for (const linkRef of graph.linkElements ?? []) {
116+
for (const linkRef of graph.linkElements() ?? []) {
119117
const g = linkRef.nativeElement as SVGGElement;
120118
const path = g.querySelector('path');
121119
expect(path?.getAttribute('d')?.length).toBeGreaterThan(0);
@@ -147,7 +145,7 @@ describe('GraphComponent drawComplete', () => {
147145
[useLayoutTransitions]="useLayoutTransitions"
148146
></ngx-graph>
149147
`,
150-
standalone: false
148+
imports: [GraphComponent]
151149
})
152150
class TestGraphLayoutJsHostComponent {
153151
syncLayout = new TestSyncLayout();
@@ -164,7 +162,7 @@ class TestGraphLayoutJsHostComponent {
164162
template: `
165163
<ngx-graph [view]="[400, 300]" [nodes]="nodes" [links]="links" [layout]="syncLayout" [animate]="false"></ngx-graph>
166164
`,
167-
standalone: false
165+
imports: [GraphComponent]
168166
})
169167
class TestGraphParseTranslateHostComponent {
170168
syncLayout = new TestLayoutWithCustomParseTranslate();
@@ -178,8 +176,7 @@ class TestGraphParseTranslateHostComponent {
178176
describe('GraphComponent layout-js-driven host class', () => {
179177
beforeEach(async () => {
180178
await TestBed.configureTestingModule({
181-
imports: [GraphModule],
182-
declarations: [TestGraphLayoutJsHostComponent]
179+
imports: [TestGraphLayoutJsHostComponent]
183180
}).compileComponents();
184181
});
185182

@@ -217,8 +214,7 @@ const DEFAULT_EDGE_PATH_SAMPLE_COUNT = 48;
217214
describe('GraphComponent redrawEdge (curve + resampling)', () => {
218215
beforeEach(async () => {
219216
await TestBed.configureTestingModule({
220-
imports: [GraphModule],
221-
declarations: [TestGraphLayoutJsHostComponent]
217+
imports: [TestGraphLayoutJsHostComponent]
222218
}).compileComponents();
223219
});
224220

@@ -310,8 +306,7 @@ describe('GraphComponent redrawEdge (curve + resampling)', () => {
310306
describe('GraphComponent resolveTranslateFromTransform', () => {
311307
beforeEach(async () => {
312308
await TestBed.configureTestingModule({
313-
imports: [GraphModule],
314-
declarations: [TestGraphParseTranslateHostComponent]
309+
imports: [TestGraphParseTranslateHostComponent]
315310
}).compileComponents();
316311
});
317312

@@ -332,8 +327,7 @@ describe('GraphComponent resolveTranslateFromTransform', () => {
332327
describe('GraphComponent layout anchor helpers (full-scope edge morph)', () => {
333328
beforeEach(async () => {
334329
await TestBed.configureTestingModule({
335-
imports: [GraphModule],
336-
declarations: [TestGraphLayoutJsHostComponent]
330+
imports: [TestGraphLayoutJsHostComponent]
337331
}).compileComponents();
338332
});
339333

0 commit comments

Comments
 (0)