Using GoJS with Angular
Examples of most of the topics discussed on this page can be found in the gojs-angular-basic project, which serves as a simple starter project.
If you are new to GoJS, it may be helpful to first visit the Getting Started Tutorial .
The easiest way to get a component set up for a GoJS Diagram is to use the gojs-angular package, which exports Angular components for GoJS Diagrams, Palettes, and Overviews. More information about the package, including the various props it takes, can be found on the NPM page. Examples here will be using a GraphLinksModel, but any model can be used.
General information
Installation
To use the published components, make sure you install GoJS and gojs-angular:
npm install gojs gojs-angular.
About component styling
Whether you are using a Diagram, Palette, or Overview gojs-angular component, you will
probably
want to style them.
First, you'll need to style a CSS class for the div of your GoJS Diagram / Palette / Overview such as:
To style the GoJS Diagram / Palette / Overview div, which will reside in the gojs-angular
component(s) you are using, make sure you set the @Component decorator's encapsulation
property to either
ViewEncapsulation.None or ViewEncapsulation.ShadowDom. Without this, your
styling will not affect the component divs.
Read more about Angular view encapsulation here.
Your @Component decorator for the component holding the your GoJS / Angular component(s)
should
look something
like:
Note: You may alternatively use the default ViewEncapsulation.Emulated
value, if you assign additional CSS modifiers :host and ::ng-deep to your class selector. Be warned,
however, ng-deep is technically deprecated, so this is not best practice.
The DataSyncService
The gojs-angular package comes with an Angular service
called DataSyncService, used to
easily merge changes (an IncrementalData
instance) with an Array of Node or Link Data, or a Model.modelData object.
This service has three static functions:
syncNodeData(changes, array)- Merges any node data changes in an IncrementalData object with a given array of node data, then returns the new array.syncLinkData(changes, array)- Merges any link data changes in an IncrementalData object with a given array of link data, then returns the new array. Note: Ensure you set the GraphLinksModel.linkKeyProperty if you are using GraphLinksModel, so data merging is possible.syncModelData(changes, object)- Merges any modelData changes in an IncrementalData object with a given modelData object, then returns the new object.
These functions should allow you to keep your data synced up as needed, without needing to write lots of code.
Listening for Model changes
It is common to listen for data changes in a Diagram or Palette, then do
something with
those changes on an application-level (such as syncing those changes with app-level data). That's why,
for both the DiagramComponent
and
PaletteComponent, there is a modelChange @Input property function. This is a
prime example of where the DataSyncService can be used.
As of gojs-angular 2.0, Array / object @Input
properties are assumed to be immutable. As such, when updating these properties, new Arrays / objects
must be generated -- one cannot simply mutate an element of the Array or object.
Please see the gojs-angular-basic
project for examples of both maintaining state immutability and usage of the DataSyncService.
Note: The UndoManager should always be enabled to allow for transactions to take place, but its UndoManager.maxHistoryLength can be set to 0 to prevent undo and redo.
The Diagram and Palette components
The Diagram and Palette components accept a similar set of @Input properties.
Diagram component accepts:
initDiagram- A function that must return a GoJS Diagram. You may define your Diagram's Node and Link templates here.divClassName- A class name for your Diagram div.nodeDataArray- An array containing data objects for your nodes.linkDataArray- An array containing data objects for your links. Optional.modelData- A data object, containing your diagram's Model.modelData. Optional.skipsDiagramUpdate- A boolean flag, specifying whether the component should skip updating, often set when updating state from a GoJS model change.modelChange- A function, which accepts an IncrementalData object. This function will fire when your Diagram's model changes, allowing you to decide what to do with those changes. A common practice is to sync your app-level data to reflect the changes in the diagram model, which is made simple using the DataSyncServicegojs-angularships with.
The Palette component accepts:
initPalette- A function that must return a GoJS Palette. You may define your Palette's Node and Link templates here.divClassName- A class name for the div your Palette div.nodeDataArray- An array containing data objects for your nodes.linkDataArray- An array containing data objects for your links. Optional.modelData- A data object, containing your palette's Model.modelData. Optional.
Because GoJS Palettes are read-only by default, there is no modelChange property in PaletteComponent.
Since there won't be user-driven changes to a Palette's model,
changes to node/link/model data should be achieved by immutably altering the analogous above @Input properties.
Sample Diagram / Palette component usage
Here is an example of how one might set up their Diagram / Palette component properties
Once you've defined your @Input properties for your components, pass these properties
to your DiagramComponent and PaletteComponent in your template, like so:
You will now have a GoJS Diagram and Palette working in your Angular application. Again, for a full
example of a gojs-angular application, see gojs-angular-basic.
A note on Diagram reinitialization
Occasionally you may want to treat a model update as if you were loading a completely new model.
But initialization is only done in your initDiagram function,
within the DiagramComponent's ngAfterViewInit lifecycle hook, and only once.
A regular model update is not treated as an initialization, so none of the initial... properties
of your Diagram will apply.
To address this problem, DiagramComponent exposes a clear method.
When called, it clears its diagram of all nodes, links, and model data, and
prepares the next state update to be treated as a diagram initialization.
That will result in an initial layout and perform initial diagram content alignment and scaling.
Note that initDiagram is not called again.
Here is a small sample of how one might trigger diagram reinitialization using the clear method with
gojs-angular 2.0.
Using the Overview component
The Overview component accepts the following Angular @Input() properties.
initOverview- A function that must return a GoJS Overview.divClassName- A class name for your Overview div.observedDiagram- The GoJS Diagram this Overview observes.
Define these properties in the component that will hold your Overview component, like:
Then pass these properties to your Overview Component in your template, like:
But, we're not done yet. observedDiagram is null, so the Overview won't observe
anything.
To assign your Overview a Diagram to observe, you will have to reassign the observedDiagram
property after initialization. To do so,
reassign the bound observedDiagram property in your component holding your Overview
Component in the ngAfterViewInit lifecycle hook.
Note: To avoid a ExpressionChangedAfterItHasBeenCheckedError, you
must
inform
Angular
to then detect changes.
This can be done with the ChangeDetectorRef.detectChanges()
method. You can inject a ChangeDetectorRef instance
into your wrapper Component constructor, and use that after you alter observedDiagram
to
call
detectChanges(). Like so:
Now, after initialization, your Overview should display appropriately.
Updating properties based on app state
You may have some app-level properties you want to affect the behavior / appearance of your
Diagram,
Palette,
or Overview. You could subclass their respective components and add @Input bindings
with
specific
setter methods, or, more simply, you can have an ngOnChanges function in your
app-level
component
that updates various Diagram / Palette / Component properties based on your app state.
For example, say you have an app-level property called showGrid. When showGrid
is
true, your Diagram's grid should be visible -- when false, it should be invisible. In your
AppComponent, you
could do something like: