Skip to content

Commit a89f010

Browse files
committed
gojs-angular-basic v2.0
1 parent 911ec88 commit a89f010

11 files changed

+202
-135
lines changed

package-lock.json

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"gojs-angular": "^1.0.17",
2525
"rxjs": "^6.6.3",
2626
"tslib": "^2.0.0",
27-
"zone.js": "~0.11.2"
27+
"zone.js": "~0.11.2",
28+
"immer": "9.0.1"
2829
},
2930
"devDependencies": {
3031
"@angular-devkit/build-angular": "^0.1100.2",

src/app/app.component.html

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,14 @@ <h1>GoJS / Angular - Components Sample</h1>
22

33
<div class="left">
44
Palette
5-
<gojs-palette #myPalette [initPalette]='initPalette' [divClassName]='paletteDivClassName' [nodeDataArray]='paletteNodeData'
6-
[linkDataArray]='paletteLinkData' [modelData]='paletteModelData' (modelChange)='paletteModelChange($event)' [skipsPaletteUpdate]='skipsPaletteUpdate'></gojs-palette>
5+
<gojs-palette #myPalette [initPalette]='initPalette' [divClassName]='paletteDivClassName' [nodeDataArray]='state.paletteNodeData'
6+
[modelData]='state.paletteModelData' (modelChange)='paletteModelChange($event)' [skipsPaletteUpdate]='state.skipsPaletteUpdate'></gojs-palette>
77
</div>
88

9-
10-
119
<div class="left">
1210
Diagram
13-
<gojs-diagram #myDiagram [initDiagram]='initDiagram' [nodeDataArray]='diagramNodeData' [linkDataArray]='diagramLinkData'
14-
[divClassName]='diagramDivClassName' [modelData]='diagramModelData' [skipsDiagramUpdate]='skipsDiagramUpdate'
11+
<gojs-diagram #myDiagram [initDiagram]='initDiagram' [nodeDataArray]='state.diagramNodeData' [linkDataArray]='state.diagramLinkData'
12+
[divClassName]='diagramDivClassName' [modelData]='state.diagramModelData' [(skipsDiagramUpdate)]='state.skipsDiagramUpdate'
1513
(modelChange)='diagramModelChange($event)'>
1614
</gojs-diagram>
1715
</div>
@@ -23,14 +21,14 @@ <h1>GoJS / Angular - Components Sample</h1>
2321

2422

2523
<div class="left">
26-
<app-inspector [selectedNode]='selectedNode' (onFormChange)='handleInspectorChange($event)'></app-inspector>
24+
<app-inspector [nodeData]='state.selectedNodeData' (onInspectorChange)='handleInspectorChange($event)'></app-inspector>
2725
</div>
2826

2927

3028
<div class="clear left box">
3129
<h3>Links in app data</h3>
3230
<ul>
33-
<li *ngFor="let ld of diagramLinkData">
31+
<li *ngFor="let ld of state.diagramLinkData">
3432
Link Key: {{ld.key}}, To: {{ld.to}}, From: {{ ld.from }}
3533
</li>
3634
</ul>
@@ -39,8 +37,8 @@ <h3>Links in app data</h3>
3937
<div class="left box">
4038
<h3>Nodes in app data</h3>
4139
<ul>
42-
<li *ngFor="let nd of diagramNodeData">
43-
Node Key: {{nd.key}}, Text: {{nd.text}}, Color: {{nd.color}}
40+
<li *ngFor="let nd of state.diagramNodeData">
41+
Node ID: {{nd.id}}, Text: {{nd.text}}, Color: {{nd.color}}
4442
</li>
4543
</ul>
4644
</div>

src/app/app.component.ts

Lines changed: 131 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
/**
2+
* Sample app showcasing gojs-angular components
3+
* For use with gojs-angular version 2.x
4+
*/
5+
16
import { ChangeDetectorRef, Component, ViewChild, ViewEncapsulation } from '@angular/core';
27
import * as go from 'gojs';
38
import { DataSyncService, DiagramComponent, PaletteComponent } from 'gojs-angular';
4-
import * as _ from 'lodash';
9+
import produce from "immer";
510

611
@Component({
712
selector: 'app-root',
@@ -14,14 +19,57 @@ export class AppComponent {
1419
@ViewChild('myDiagram', { static: true }) public myDiagramComponent: DiagramComponent;
1520
@ViewChild('myPalette', { static: true }) public myPaletteComponent: PaletteComponent;
1621

22+
// Big object that holds app-level state data
23+
// As of gojs-angular 2.0, immutability is expected and required of state for ease of change detection.
24+
// Whenever updating state, immutability must be preserved. It is recommended to use immer for this, a small package that makes working with immutable data easy.
25+
public state = {
26+
// Diagram state props
27+
diagramNodeData: [
28+
{ id: 'Alpha', text: "Alpha", color: 'lightblue' },
29+
{ id: 'Beta', text: "Beta", color: 'orange' },
30+
{ id: 'Gamma', text: "Gamma", color: 'lightgreen' },
31+
{ id: 'Delta', text: "Delta", color: 'pink' }
32+
],
33+
diagramLinkData: [
34+
{ key: -1, from: 'Alpha', to: 'Beta', fromPort: 'r', toPort: '1' },
35+
{ key: -2, from: 'Alpha', to: 'Gamma', fromPort: 'b', toPort: 't' },
36+
{ key: -3, from: 'Beta', to: 'Beta' },
37+
{ key: -4, from: 'Gamma', to: 'Delta', fromPort: 'r', toPort: 'l' },
38+
{ key: -5, from: 'Delta', to: 'Alpha', fromPort: 't', toPort: 'r' }
39+
],
40+
diagramModelData: { prop: 'value' },
41+
skipsDiagramUpdate: false,
42+
selectedNodeData: null, // used by InspectorComponent
43+
44+
// Palette state props
45+
paletteNodeData: [
46+
{ key: 'PaletteNode1', color: 'firebrick' },
47+
{ key: 'PaletteNode2', color: 'blueviolet' }
48+
],
49+
paletteLinkData: [
50+
{
51+
points: new go.List(/*go.Point*/).addAll([new go.Point(0, 0), new go.Point(30, 0), new go.Point(30, 40), new go.Point(60, 40)]),
52+
strokeWidth: 4, dash: [6, 3]
53+
},
54+
{ points: new go.List(/*go.Point*/).addAll([new go.Point(20, 20), new go.Point(60, 20), new go.Point(60, 40), new go.Point(60, 50)]) }
55+
],
56+
paletteModelData: { prop: 'val' },
57+
skipsPaletteUpdate: false
58+
};
59+
60+
public diagramDivClassName: string = 'myDiagramDiv';
61+
public paletteDivClassName = 'myPaletteDiv';
62+
1763
// initialize diagram / templates
1864
public initDiagram(): go.Diagram {
1965

2066
const $ = go.GraphObject.make;
2167
const dia = $(go.Diagram, {
2268
'undoManager.isEnabled': true,
69+
'clickCreatingTool.archetypeNodeData': { text: 'new node', color: 'lightblue' },
2370
model: $(go.GraphLinksModel,
2471
{
72+
nodeKeyProperty: 'id',
2573
linkToPortIdProperty: 'toPort',
2674
linkFromPortIdProperty: 'fromPort',
2775
linkKeyProperty: 'key' // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
@@ -58,10 +106,13 @@ export class AppComponent {
58106
},
59107
$(go.Panel, 'Auto',
60108
$(go.Shape, 'RoundedRectangle', { stroke: null },
61-
new go.Binding('fill', 'color')
109+
new go.Binding('fill', 'color', (c, panel) => {
110+
111+
return c;
112+
})
62113
),
63-
$(go.TextBlock, { margin: 8 },
64-
new go.Binding('text'))
114+
$(go.TextBlock, { margin: 8, editable: true },
115+
new go.Binding('text').makeTwoWay())
65116
),
66117
// Ports
67118
makePort('t', go.Spot.TopCenter),
@@ -73,37 +124,19 @@ export class AppComponent {
73124
return dia;
74125
}
75126

76-
public diagramNodeData: Array<go.ObjectData> = [
77-
{ key: 'Alpha', text: "Node Alpha", color: 'lightblue' },
78-
{ key: 'Beta', text: "Node Beta", color: 'orange' },
79-
{ key: 'Gamma', text: "Node Gamma", color: 'lightgreen' },
80-
{ key: 'Delta', text: "Node Delta", color: 'pink' }
81-
];
82-
public diagramLinkData: Array<go.ObjectData> = [
83-
{ key: -1, from: 'Alpha', to: 'Beta', fromPort: 'r', toPort: 'l' },
84-
{ key: -2, from: 'Alpha', to: 'Gamma', fromPort: 'b', toPort: 't' },
85-
{ key: -3, from: 'Beta', to: 'Beta' },
86-
{ key: -4, from: 'Gamma', to: 'Delta', fromPort: 'r', toPort: 'l' },
87-
{ key: -5, from: 'Delta', to: 'Alpha', fromPort: 't', toPort: 'r' }
88-
];
89-
public diagramDivClassName: string = 'myDiagramDiv';
90-
public diagramModelData = { prop: 'value' };
91-
public skipsDiagramUpdate = false;
92-
93-
// When the diagram model changes, update app data to reflect those changes
127+
// When the diagram model changes, update app data to reflect those changes. Be sure to use immer's "produce" function to preserve immutability
94128
public diagramModelChange = function(changes: go.IncrementalData) {
95-
// when setting state here, be sure to set skipsDiagramUpdate: true since GoJS already has this update
96-
// (since this is a GoJS model changed listener event function)
97-
// this way, we don't log an unneeded transaction in the Diagram's undoManager history
98-
this.skipsDiagramUpdate = true;
99-
100-
this.diagramNodeData = DataSyncService.syncNodeData(changes, this.diagramNodeData);
101-
this.diagramLinkData = DataSyncService.syncLinkData(changes, this.diagramLinkData);
102-
this.diagramModelData = DataSyncService.syncModelData(changes, this.diagramModelData);
129+
const appComp = this;
130+
this.state = produce(this.state, draft => {
131+
// set skipsDiagramUpdate: true since GoJS already has this update
132+
// this way, we don't log an unneeded transaction in the Diagram's undoManager history
133+
draft.skipsDiagramUpdate = true;
134+
draft.diagramNodeData = DataSyncService.syncNodeData(changes, draft.diagramNodeData, appComp.observedDiagram.model);
135+
draft.diagramLinkData = DataSyncService.syncLinkData(changes, draft.diagramLinkData, appComp.observedDiagram.model);
136+
draft.diagramModelData = DataSyncService.syncModelData(changes, draft.diagramModelData);
137+
});
103138
};
104139

105-
106-
107140
public initPalette(): go.Palette {
108141
const $ = go.GraphObject.make;
109142
const palette = $(go.Palette);
@@ -118,35 +151,53 @@ export class AppComponent {
118151
new go.Binding('fill', 'color')
119152
),
120153
$(go.TextBlock, { margin: 8 },
121-
new go.Binding('text'))
154+
new go.Binding('text', 'key'))
122155
);
123156

157+
palette.linkTemplate = $(go.Link,
158+
{ // because the GridLayout.alignment is Location and the nodes have locationSpot == Spot.Center,
159+
// to line up the Link in the same manner we have to pretend the Link has the same location spot
160+
locationSpot: go.Spot.Center,
161+
selectionAdornmentTemplate:
162+
$(go.Adornment, 'Link',
163+
{ locationSpot: go.Spot.Center },
164+
$(go.Shape,
165+
{ isPanelMain: true, fill: null, stroke: 'deepskyblue', strokeWidth: 0 }, new go.Binding('strokeDashArray', 'dash')),
166+
$(go.Shape, // the arrowhead
167+
{ toArrow: 'Standard', stroke: null })
168+
)
169+
},
170+
{
171+
routing: go.Link.AvoidsNodes,
172+
curve: go.Link.JumpOver,
173+
corner: 5,
174+
toShortLength: 4
175+
},
176+
new go.Binding('points'),
177+
$(go.Shape, // the link path shape
178+
{ isPanelMain: true }, new go.Binding('strokeDashArray', 'dash')),
179+
$(go.Shape, // the arrowhead
180+
{ toArrow: 'Standard', stroke: null })
181+
)
182+
124183
palette.model = $(go.GraphLinksModel,
125184
{
126185
linkKeyProperty: 'key' // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
127186
});
128187

129188
return palette;
130189
}
131-
public paletteNodeData: Array<go.ObjectData> = [
132-
{ key: 'PaletteNode1', text: "PaletteNode1", color: 'red' },
133-
{ key: 'PaletteNode2', text: "PaletteNode2", color: 'yellow' }
134-
];
135-
public paletteLinkData: Array<go.ObjectData> = [
136-
{ }
137-
];
138-
public paletteModelData = { prop: 'val' };
139-
public paletteDivClassName = 'myPaletteDiv';
140-
public skipsPaletteUpdate = false;
141-
public paletteModelChange = function(changes: go.IncrementalData) {
142-
// when setting state here, be sure to set skipsPaletteUpdate: true since GoJS already has this update
143-
// (since this is a GoJS model changed listener event function)
144-
// this way, we don't log an unneeded transaction in the Palette's undoManager history
145-
this.skipsPaletteUpdate = true;
146-
147-
this.paletteNodeData = DataSyncService.syncNodeData(changes, this.paletteNodeData);
148-
this.paletteLinkData = DataSyncService.syncLinkData(changes, this.paletteLinkData);
149-
this.paletteModelData = DataSyncService.syncModelData(changes, this.paletteModelData);
190+
191+
// When the palette model changes, update app data to reflect those changes. Be sure to use immer's "produce" function to preserve immutability
192+
public paletteModelChange = function(changes: go.IncrementalData) {
193+
this.state = produce(this.state, draft => {
194+
// set skipsPaletteUpdate: true since GoJS already has this update
195+
// this way, we don't log an unneeded transaction in the Palette's undoManager history
196+
draft.skipsPaletteUpdate = true;
197+
draft.paletteNodeData = DataSyncService.syncNodeData(changes, draft.paletteNodeData);
198+
draft.paletteLinkData = DataSyncService.syncLinkData(changes, draft.paletteLinkData);
199+
draft.paletteModelData = DataSyncService.syncModelData(changes, draft.paletteModelData);
200+
});
150201
};
151202

152203
constructor(private cdr: ChangeDetectorRef) { }
@@ -161,10 +212,9 @@ export class AppComponent {
161212
public observedDiagram = null;
162213

163214
// currently selected node; for inspector
164-
public selectedNode: go.Node | null = null;
215+
public selectedNodeData: go.ObjectData = null;
165216

166217
public ngAfterViewInit() {
167-
168218
if (this.observedDiagram) return;
169219
this.observedDiagram = this.myDiagramComponent.diagram;
170220
this.cdr.detectChanges(); // IMPORTANT: without this, Angular will throw ExpressionChangedAfterItHasBeenCheckedError (dev mode only)
@@ -173,40 +223,41 @@ export class AppComponent {
173223
// listener for inspector
174224
this.myDiagramComponent.diagram.addDiagramListener('ChangedSelection', function(e) {
175225
if (e.diagram.selection.count === 0) {
176-
appComp.selectedNode = null;
226+
appComp.selectedNodeData = null;
177227
}
178228
const node = e.diagram.selection.first();
179-
if (node instanceof go.Node) {
180-
appComp.selectedNode = node;
181-
} else {
182-
appComp.selectedNode = null;
183-
}
229+
appComp.state = produce(appComp.state, draft => {
230+
if (node instanceof go.Node) {
231+
var idx = draft.diagramNodeData.findIndex(nd => nd.id == node.data.id);
232+
var nd = draft.diagramNodeData[idx];
233+
draft.selectedNodeData = nd;
234+
} else {
235+
draft.selectedNodeData = null;
236+
}
237+
});
184238
});
185-
186239
} // end ngAfterViewInit
187240

188241

189-
public handleInspectorChange(newNodeData) {
190-
const key = newNodeData.key;
191-
// find the entry in nodeDataArray with this key, replace it with newNodeData
192-
let index = null;
193-
for (let i = 0; i < this.diagramNodeData.length; i++) {
194-
const entry = this.diagramNodeData[i];
195-
if (entry.key && entry.key === key) {
196-
index = i;
197-
}
198-
}
242+
/**
243+
* Update a node's data based on some change to an inspector row's input
244+
* @param changedPropAndVal An object with 2 entries: "prop" (the node data prop changed), and "newVal" (the value the user entered in the inspector <input>)
245+
*/
246+
public handleInspectorChange(changedPropAndVal) {
199247

200-
if (index >= 0) {
201-
// here, we set skipsDiagramUpdate to false, since GoJS does not yet have this update
202-
this.skipsDiagramUpdate = false;
203-
this.diagramNodeData[index] = _.cloneDeep(newNodeData);
204-
// this.diagramNodeData[index] = _.cloneDeep(newNodeData);
205-
}
206-
207-
// var nd = this.observedDiagram.model.findNodeDataForKey(newNodeData.key);
208-
// console.log(nd);
248+
const path = changedPropAndVal.prop;
249+
const value = changedPropAndVal.newVal;
209250

251+
this.state = produce(this.state, draft => {
252+
var data = draft.selectedNodeData;
253+
data[path] = value;
254+
const key = data.id;
255+
const idx = draft.diagramNodeData.findIndex(nd => nd.id == key);
256+
if (idx >= 0) {
257+
draft.diagramNodeData[idx] = data;
258+
draft.skipsDiagramUpdate = false; // we need to sync GoJS data with this new app state, so do not skips Diagram update
259+
}
260+
});
210261
}
211262

212263

src/app/app.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import { GojsAngularModule } from 'gojs-angular';
55
import { AppComponent } from './app.component';
66

77
import { InspectorComponent } from './inspector/inspector.component';
8+
import { InspectorRowComponent } from './inspector/inspector-row.component';
89

910

1011
@NgModule({
1112
declarations: [
1213
AppComponent,
13-
InspectorComponent
14+
InspectorComponent,
15+
InspectorRowComponent
1416
],
1517
imports: [
1618
BrowserModule,

src/app/inspector/inspector-row.component.css

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<td>{{id}}</td>
2+
<td>
3+
<input
4+
(change)="onInputChange($event)"
5+
[ngModel]="value"
6+
/>
7+
</td>
8+

0 commit comments

Comments
 (0)