Skip to main content

JsPlumb Angular

JsPlumb has a service and several components/directives to assist you in integrating with Angular 16+. At the time of writing, our Angular integration has been tested with Angular 16, 17, 18 and 19.

Imports

"dependencies":{
"@jsplumbtoolkit/browser-ui-angular":"7.2.0"
}

Setup

You need to import the jsPlumbToolkitModule:

import { jsPlumbToolkitModule } from '@jsplumbtoolkit/browser-ui-angular';

...

@NgModule({
imports: [ BrowserModule, jsPlumbToolkitModule],
declarations: [ SomeComponent ],
bootstrap: [ SomeComponent ],
schemas:[ CUSTOM_ELEMENTS_SCHEMA ]
})
note

Be sure to import the CUSTOM_ELEMENTS_SCHEMA schema. Without this import you will not be able to map components to node/group types.


Quick start

Here's a basic setup that uses the jsplumb-surface component to render a couple of nodes and an edge:


import {Component} from "@angular/core"

@Component({
template:`<div style={{width:"100%",height:"100%"}}>
<jsplumb-surface [data]="data"/>
<jsplumb-controls/>
<jsplumb-miniview/>
</div>`
})
export class DemoComponent {

data = {
nodes:[
{ id:"1", label:"1", left:50, top:50},
{ id:"2", label:"TWO", left:250, top:250}
],
edges:[
{ source:"1", target:"2" }
]
}

}

In the above example the only attributes we set on the jsplumb-surface was data, meaning the component uses a default Absolute layout and a default Angular component for rendering nodes. The jsplumb-surface, though, is highly configurable, mainly through its view and renderParams inputs. Here's the same example from before but with a few more things configured, and an Angular component to render nodes with:

NodeComponent

import {Component} from "@angular/core"

import { BaseNodeComponent } from '@jsplumbtoolkit/browser-ui-angular';

@Component({
template:`<div style={{width:"60px", height:"60px", backgroundColor:"white", border:"1px solid black", text-align:"center"}}>
<h3>{{obj.label}}</h3>
</div>`
})
export class NodeComponent extends BaseNodeComponent {

}
DemoComponent

import {Component} from "@angular/core"

import { NodeComponent } from './node.component';

@Component({
template:`<div style={{width:"100%",height:"100%"}}>

<jsplumb-surface [data]="data"
[renderParams]="renderParams"
[view]="view"/>

<jsplumb-controls/>
<jsplumb-miniview/>
</div>`
})
export class DemoComponent {

view = {
nodes:{
default:{
component:NodeComponent
}
}
}

data = {
nodes:[
{ id:"1", label:"1", left:50, top:50},
{ id:"2", label:"TWO", left:250, top:250}
],
edges:[
{ source:"1", target:"2" }
]
}

renderOptions = {
layout:{
type:"ForceDirected"
},
grid:{
size:{
w:50, h:50
}
}
}

}


Rendering Nodes and Groups

At the core of your UI will be a jsplumb-surface, which provides the canvas onto which nodes, groups and edges are drawn. In the Quickstart section above we show a couple of examples of the jsplumb-surface in action.

Each node or group in your UI is rendered as an individual Angular component. In the first example above, we rely on JsPlumb's default Angular component to render each node, but in the second we provide a component ourselves, although its quite simple. In a real world app your component will likely be more complex, and can take full advantage of all the power that Angular offers. As an example, consider the component we use to render nodes in the Flowchart Builder:

import {Component} from "@angular/core"
import {BaseNodeComponent} from "@jsplumbtoolkit/browser-ui-angular"

import { anchorPositions } from "./app.component"

@Component({
template:`<div style="color:{{obj.textColor}}" class="flowchart-object" data-jtk-target="true">

<jtk-shape [obj]="obj" showLabels="true" labelProperty="text" [width]="obj.width" [height]="obj.height"></jtk-shape>

<div *ngFor="let anchor of anchorPositions"
class="jtk-connect jtk-connect-{{anchor.id}}"
[attr.data-jtk-anchor-x]="anchor.x"
[attr.data-jtk-anchor-y]="anchor.y"
[attr.data-jtk-orientation-x]="anchor.ox"
[attr.data-jtk-orientation-y]="anchor.oy"
data-jtk-source="true"></div>

<div class="node-delete node-action delete" (click)="this.removeNode()"></div>
</div>`
})
export class NodeComponent extends BaseNodeComponent {
anchorPositions = anchorPositions
}

The components you use to render nodes with JsPlumb Angular are entirely at your discretion, and can be as simple or as powerful as you like.

Mapping types to components

The view input is used to map node/group types to components, and to certain behaviours. For instance, this is the nodes section of the view for the flowchart builder starter app:

view = {
nodes: {
[DEFAULT]:{
component:NodeComponent,
// target connections to this node can exist at any of the given anchorPositions
anchorPositions,
// node can support any number of connections.
maxConnections: -1,
events: {
[EVENT_TAP]: (params) => {
this.edgeEditor.stopEditing()
// if zero nodes currently selected, or the shift key wasnt pressed, make this node the only one in the selection.
if (this.toolkit.getSelection()._nodes.length < 1 || params.e.shiftKey !== true) {
this.toolkit.setSelection(params.obj)
} else {
// if multiple nodes already selected, or shift was pressed, add this node to the current selection.
this.toolkit.addToSelection(params.obj)
}
}
}
}
}
}

To map a node/group type to a component, you supply a reference to the component's class as the component in a mapping, as shown above.

Using @Input in a node/group component

There are two approaches to injecting values into a node/group component.

Injecting values from the backing data

Imagine the component shown above was being used to render some node type in your UI - with a couple of small changes we can make it so:

import {BaseNodeComponent} from "@jsplumbtoolkit/browser-ui-angular"
import {EventEmitter, Component, Input, Output} from "@angular/core"

@Component({
template:`
<div>
<h1>{{title}}</h1>
<button class="btnEmit" (click)="emitEvent('test')">Click Me</button>
</div>
`
})
export class NodeComponent extends BaseNodeComponent {
@Input() title:string

@Output() outputEvent = new EventEmitter<string>()

emitEvent(value:string) {
this.outputEvent.emit(value)
}
}

We've removed selector from the Component spec, since it's not relevant when rendering via the Surface, and we've wired up the component to extend BaseNodeComponent, but other than that the component is the same as in the first example.

JsPlumb will attempt to set this input from the backing data for the node. So if, for instance, you had this data object for a node:

{
"id": "1",
"title": "My First Node"
}

then JsPlumb will set "My First Node" on the title input. This is of course really just a form of syntactic sugar - you could just easily render the title in your template like this (remembering that obj is set on the component by the Toolkit when it creates it):

<h1>{{obj.title}}</h1>

as you can by using the input on the class:

<h1>{{title}}</h1>

but if you're like me, you might find that the approach using @Input feels a little more nicely formalised.

Injecting values from the view

An alternative approach to injecting values from a node or group's backing data is to provide a string or a function inside of the view from which JsPlumb will get the value to use. Consider again the component from above:

import {BaseNodeComponent} from "@jsplumbtoolkit/browser-ui-angular"
import {EventEmitter, Component, Input, Output} from "@angular/core"

@Component({
template:`
<div>
<h1>{{title}}</h1>
<button class="btnEmit" (click)="emitEvent('test')">Click Me</button>
</div>
`
})
export class NodeComponent extends BaseNodeComponent {
@Input() title:string

@Output() outputEvent = new EventEmitter<string>()

emitEvent(value:string) {
this.outputEvent.emit(value)
}
}

We can map this component in our view and provide a function to populate title at render time:

view:{
nodes:{
default:{
component:NodeComponent,
inputs:{
title:(n:Node) => 'here you can return any string you like'
}
}
}
}

Using @Output in a node/group component

You can also now wire up listeners to @Outputs in your components. The process to do this involves providing a vertexEvent callback to the Surface component. For example, imagine you embed a surface component in one of your components:

<div class="my-component">
<h3>Here is my component</h3>
<jsplumb-surface [view]="view"
[renderParams]="renderParams"
(vertexEvent)="vertexEvent($event)">
</jsplumb-surface>
</div>

Remember from above the @Output() outputEvent... declaration on the node component? Whenever that output emits something, the Surface will invoke vertexEvent:


import { Vertex } from "@jsplumbtoolkit/browser-ui"
import { ComponentRef } from "@angular/core"

vertexEvent(detail:{
vertex:Vertex,
event:Event,
payload:any,
component:ComponentRef<any>}) {

}

The callback is given JsPlumb's vertex, the original event, the payload (ie. whatever was emitted from the output), and the Angular ComponentRef. You can access the specific Angular component via the instance field on this component ref.


Rendering Ports

Some applications whose data model use ports have a UI in which each Port is assigned its own DOM element. In the Flowchart Builder application we use ports in the data model (a question node, for instance, has a Yes and a No output port), but we do not assign a DOM element to each port. In the Schema Builder, though, we do: we model a database table as a node, and the columns on that table as ports, and each port is assigned its own DOM element:

Database Visualizer Table Node

Here we see three columns, each of which has a button by which it can be deleted, and a button that launches a column name editor. These two bits of behaviour are handled by the component backing the port.

Definition

This is the definition of the component used to render columns on table nodes:


import { BasePortComponent } from "@jsplumbtoolkit/browser-ui-angular";

@Component({
selector:"db-column",
templateUrl:"templates/column.html"
})
export class ColumnComponent extends BasePortComponent {

constructor(el: ElementRef) {
super(el);
}

remove() { ... }

editName() { ... }
}

There are three requirements for a component to be used to render a port:

  • you must extend BasePortComponent
  • your constructor must accept an ElementRef and call the superclass constructor with it
  • you must declare a selector for the component

Template

The component definition maps a templateUrl. This is what the template looks like in this example:

<div class="table-column table-column-type-{{obj.datatype}}"
attr.primary-key="{{obj.primaryKey}}" attr.data-jtk-port="{{obj.id}}"
data-jtk-source="true" attr.data-jtk-scope="{{obj.datatype}}" data-jtk-target="true">

<div class="table-column-delete" (click)="remove()">
<i class="fa fa-times table-column-delete-icon"></i>
</div>

<div><span>{{obj.name}}</span></div>

<div class="table-column-edit" (click)="editName()">
<i class="fa fa-pencil table-column-edit-icon"></i>
</div>

</div>

Mapping to a type

You map components to port types in the view. Here's the ports section from the view in the Angular Database Visualizer application:

ports: {
"default": {
component: ColumnComponent,
paintStyle: { fill: "#f76258" }, // the endpoint's appearance
hoverPaintStyle: { fill: "#434343" }, // appearance when mouse hovering on endpoint or connection
edgeType: "common", // the type of edge for connections from this port type
maxConnections: -1, // no limit on connections
dropOptions: { //drop options for the port. here we attach a css class.
hoverClass: "drop-hover"
},
events: {
"dblclick": (p:any) => {
console.log(p);
}
}
}
}

Declaration

You must declare each port component in the declarations member of your module definition. Here's the module definition for the Database Visualizer demonstration, for example:


import { DatabaseVisualizerComponent, TableNodeComponent, ViewNodeComponent, ColumnComponent } from './database-visualizer';

...

@NgModule({
imports:[ BrowserModule, jsPlumbToolkitModule, ROUTING],
declarations: [ AppComponent, TableNodeComponent, ViewNodeComponent, ColumnComponent, DatasetComponent, DatabaseVisualizerComponent ],
bootstrap: [ AppComponent ]
schemas:[ CUSTOM_ELEMENTS_SCHEMA ]
})

...

Here we see ColumnComponent listed in declarations (along with the components used to render the node types table and view).

Checklist

  • Each of your port components extends BasePortComponent from JsPlumb Angular.
  • You reference the underlying data via the member obj in your component templates
  • Each of your components is declared in the declarations list in your module definition
  • You've mapped each expected port to a component in your view


Change Detection

Default

If you're using Angular's default change detection mechanism (ChangeDetectionStrategy.Default) then your node/group/port/overlay components will be automatically updated whenever an updateNode, updateGroup, updatePort or updateEdge method is called on the Toolkit.

OnPush

If you'd prefer to use ChangeDetectionStrategy.OnPush, JsPlumb Angular can support that. The only requirement is that you need to inject a ChangeDetectorRef into any component with which you want to use OnPush change detection. For example, this is how we'd modify the ColumnComponent shown above to support OnPush change detection:


import { BasePortComponent } from "@jsplumbtoolkit/browser-ui-angular-legacy";

@Component({
changeDetection:ChangeDetectionStrategy.OnPush,
selector:"db-column",
templateUrl:"templates/column.html"
})
export class ColumnComponent extends BasePortComponent {

constructor(el: ElementRef, public changeDetectorRef:ChangeDetectorRef) {
super(el)
}

remove() { ... }

editName() { ... }
}

NOTE you must use the name changeDetectorRef for this constructor parameter, and you must also mark it public. JsPlumb discovers the change detector by name, not by type.

This also works for node/group components.


Shape libraries

A shape library is a set of named shapes that you will use to render SVG inside the vertices in your application. These are discussed in detail in the shape libraries and shape-sets pages; here we provide an overview of the available Angular integration with these modules.

Creating a shape library

In Angular, a shape library is created by registering one on the jsPlumbService:

import {Component} from '@angular/core';
import { FLOWCHART_SHAPES, BASIC_SHAPES } from "@jsplumbtoolkit/browser-ui";

@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {

constructor(public $jsplumb: jsPlumbService) {
this.$jsplumb.registerShapeLibrary([FLOWCHART_SHAPES, BASIC_SHAPES]);
}

}

Here, we've injected jsPlumbService into our component, and then invoked registerShapeLibrary on it, to register our shape library. Note that we did not provide any identifier for our shape library in the above code; if your app has only only shape library you do not need to provide an id, as JsPlumb will use a default. But if you had more than one shape library you'd want to provide an id when registering them:

import {Component} from '@angular/core';
import { FLOWCHART_SHAPES, BASIC_SHAPES } from "@jsplumbtoolkit/browser-ui";

@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {

constructor(public $jsplumb: jsPlumbService) {
this.$jsplumb.registerShapeLibrary([BASIC_SHAPES], "basic");
this.$jsplumb.registerShapeLibrary([FLOWCHART_SHAPES], "flowchart");
}

}

Rendering an SVG shape

Shapes can be rendered with a jtk-shape. For example, here'a NodeComponent that we've mapped in our view, as shown above. This NodeComponent uses a jtk-shape inside its template:

import {Component} from "@angular/core"
import {BaseNodeComponent} from "@jsplumbtoolkit/browser-ui-angular"

@Component({
template:`<div style="color:{{obj.textColor}}" class="flowchart-object" data-jtk-target="true">
<jtk-shape [obj]="obj" [width]="obj.width"
[height]="obj.height"></jtk-shape>
</div>`
})
export class NodeComponent extends BaseNodeComponent { }

width, height and obj are all required inputs.

Again note the we did not tell the jtk-shape component how to find the shape library to use - it will use the default id. But if we wanted to specify a shape library, we'd do that with the shapeLibraryId input:

import {Component} from "@angular/core"
import {BaseNodeComponent} from "@jsplumbtoolkit/browser-ui-angular"

@Component({
template:`<div style="color:{{obj.textColor}}" class="flowchart-object" data-jtk-target="true">
<jtk-shape [obj]="obj" [width]="obj.width"
[height]="obj.height"
shapeLibraryId="basic"
></jtk-shape>
</div>`
})
export class NodeComponent extends BaseNodeComponent { }

Supported props

The list of props support by the jtk-shape component is:

InputTypeDescription
shapeLibraryIdstringThe ID of the shape library to use. If not specified, a default will be used, which suffices for applications that are only using one shape library.
objObjectDataThe data for the vertex to render. Required.
widthnumberCurrent width for the shape. Although it may seem counterintuitive, this is required - you'll need to set it to `[width]="obj.width"`. Angular's change detection will not pick up changes to this otherwise.
heightnumberCurrent height. See notes for `width`.
showLabelsbooleanWhether or not to show labels.
labelPropertystringDefaults to `label`. The name of the property inside each vertex that contains its label.
labelStrokeWidthstringThe stroke width to use for labels. Optional. Defaults to the Toolkit's default.

By default the shape component will render just the shape. If you want to add labels you can do so like this:

import {Component} from "@angular/core"
import {BaseNodeComponent} from "@jsplumbtoolkit/browser-ui-angular"

@Component({
template:`<div style="color:{{obj.textColor}}" class="flowchart-object" data-jtk-target="true">

<jtk-shape [obj]="obj" showLabels="true"
[width]="obj.width"
[height]="obj.height"></jtk-shape>
</div>`
})
export class NodeComponent extends BaseNodeComponent { }

By default, JsPlumb will extract the label from a label property in your vertex data. You can provide the name of the property to use if you want to, by setting the labelProperty on your jtk-shape:

import {Component} from "@angular/core"
import {BaseNodeComponent} from "@jsplumbtoolkit/browser-ui-angular"

@Component({
template:`<div style="color:{{obj.textColor}}" class="flowchart-object" data-jtk-target="true">

<jtk-shape [obj]="obj" showLabels="true"
labelProperty="text"
[width]="obj.width"
[height]="obj.height"></jtk-shape>
</div>`
})
export class NodeComponent extends BaseNodeComponent { }

Adding your own shapes

To add your own shapes, you'll need to create a shape set. These are discussed on a separate page.

Exporting a Surface to SVG/PNG/JPG

It is possible to export a Surface that is using a shape library to SVG, PNG or JPG. For a full discussion of this, see this page. For convenience, we added several methods to our Angular integration to support this, as discussed below.

The methods are:

  • exportSvg<T extends ObjectData>(options:AngularSvgExportOptions<T>)

Exports a given surface to an SVG.

  • showSvgExportUI<T extends ObjectData>(options:AngularSvgExportUIOptions<T>)

Shows the Toolkit's SVG export UI

  • exportImage<T extends ObjectData>(options:AngularImageExportOptions<T>, onready:ImageReadyFunction)

Exports a given surface to an image (PNG or JPG)

  • showImageExportUI<T extends ObjectData>(options:AngularImageExportUIOptions<T>)

Shows the Toolkit's image export UI

  • exportPNG<T extends ObjectData>(options:AngularImageExportOptions<T>, onready:ImageReadyFunction)

Exports a given surface to a PNG

  • exportJPG<T extends ObjectData>(options:AngularImageExportOptions<T>, onready:ImageReadyFunction)

Exports a given surface to a JPG

There are four interfaces used by these methods - AngularSvgExportOptions, AngularImageExportOptions, AngularSvgExportUIOptions and AngularImageExportUIOptions. Each of these is an extension of an interface from the exporter, and each of these interfaces has these properties in common:

 /**
* Surface to export
*/
surface:Surface
/**
* Optional ShapeLibrary to use. If not supplied, supply `shapeLibraryId` - or ensure the Surface you are exporting has a ShapeLibrary registered on it.
*/
shapeLibrary?:ShapeLibrary<T>
/**
* Optional ID of a ShapeLibrary to use - must have been registered on the jsPlumb Angular service via the `registerShapeLibrary` method. If not supplied,
* supply `shapeLibrary` - or ensure the Surface you are exporting has a ShapeLibrary registered on it.
*/
shapeLibraryId?:string

The key piece to keep in mind when exporting via Angular is that your Surface most likely does not have a shape library registered directly on it, due to the way shape libraries and surface are created when using the Angular integration.

We recommend you always register a shape library on the Angular service, which you can then retrieve via its ID, and provide as shapeLibraryId to one of the export calls. But keep in mind that if you're only using one shape library, you can register it without an id and the Toolkit will use a default - and it will also use that default ID when trying to retrieve a shape library to use for an export.

For example, this is from the flowchart builder starter pp:

exportSVG() {
// use the `showSvgExportUI` method to popup the exporter window. Note that we do not need to provide a shapeLibrary
// shapeLibraryId here, as the service will use a default shape library id if none is provided. We also did not provide an
// id when we registered the shape library above, meaning it was registered with the default id.
this.$jsplumb.showSvgExportUI({
surface:this.surface
})
}

Dragging new nodes/groups

Palettes offer a means for your users to drag new nodes/groups onto your canvas. The SurfaceDropComponent allows you to configure drag/drop of HTML elements; there's also a ShapeLibraryPaletteComponent which is a specialised version of the SurfaceDropComponent that sources its draggable nodes from a ShapeLibrary.

HTML elements

To configure your UI to drag and drop HTML elements onto your canvas, you'll use a SurfaceDropComponent, which is a directive you declare via the inclusion of a specific attribute on some element in your template:

<div class="sidebar node-palette" 
jsplumb-surface-drop
selector="div">
<div *ngFor="let nodeType of nodeTypes"
[attr.data-jtk-type]="nodeType.type"
[attr.data-jtk-width]="nodeType.w"
[attr.data-jtk-height]="nodeType.h">{{nodeType.label}}</div>
</div>

The basic contract is that you declare the jsplumb-surface-drop attribute on some element, identifying it as a SurfaceDropComponent. You also need to specify the selector, which tells JsPlumb how to locate child elements that should be draggable. In the code above we used div, but any valid CSS3 selector may be used.

The SurfaceDropComponent also needs to know which surface to attach to. You can provide a surfaceId input to identify your surface, but if you do not, JsPlumb will use the default ID. For the vast majority of apps - having only a single surface - you can safely omit the surfaceId and use the default.

The SurfaceDropComponent will automatically prepare a dataset for a newly dragged node/group by reading any data-jtk-*** attributes from the element the user dragged. You can override this behaviour by providing a dataGenerator - see below.

SVG shapes

To drag and drop SVG shapes, you can use a ShapeLibraryPaletteComponent. This is a specialised version of the PaletteComponent which sources its draggable nodes from a ShapeLibrary. Unlike SurfaceDropComponent this is not a directive - it's an element you drop into your template:

@Component({
selector:"app-component",
template:`<div>
<jsplumb-surface [view]="view"></jsplumb-surface>
<jtk-shape-palette [dataGenerator]="dataGenerator"></jtk-shape-palette>
</div>`
</div>
})

export class AppComponent {
view = {... }

dataGenerator(el:Element) {
return {
...
}
}
}

dataGenerator is optional. JsPlumb will create a data object containing the type and category of any shape you drag; if you choose to provide a dataGenerator your values will be added to the data object JsPlumb creates.

note

You cannot override type or category via a dataGenerator. JsPlumb will use the values corresponding to the shape that was dragged

As with the SurfaceDropComponent, in the code shown we have not supplied a surfaceId, and this is again because the component will use a default when a value is not supplied.

InputTypeDescription
shapeLibraryIdstringThe ID of the shape library to render. If not specified, a default will be used, which suffices for applications that are only using one shape library. Defaults to `DEFAULT_SHAPE_LIBRARY_ID`
surfaceIdstringThe ID of the surface to attach to. Only required if you have more than one surface in your ui.
initialSetstringWhen the shape library contains multiple sets, use this to instruct the palette that you want it to initially load just displaying this set. Optional.
dragSizeSizeOptional size to use for dragged elements. Defaults to { w:150, h:100 }
iconSizeSizeOptional size to use for icons. Default to { w:150, h:100 }
fillstringOptional fill color to use for dragged elements. This should be in RGB format, _not_ a color like 'white' or 'cyan' etc.
outlinestringOptional color to use for outline of dragged elements. Should be in RGB format.
showAllMessagestringMessage to use for the 'show all' option in the shape set drop down when there is more than one set of shapes. Defaults to `Show all`.
selectAfterDropbooleanWhen true (which is the default), a newly dropped vertex will be set as the underlying Toolkit's selection.
canvasStrokeWidthnumberStroke width to use in shapes on the canvas. Defaults to 2.
paletteStrokeWidthnumberStroke width to use in shapes in the palette. Defaults to 1.
dataGeneratorDataGeneratorFunctionOptional function you can use to set some initial data for an element being dragged. Note that you cannot override the object's `type` with this function.

Inspectors

JsPlumb Angular includes an InspectorComponent that you can extend to make use of JsPlumb's Inspectors. It exposes currentType and currentObjectType members which you can reference in your subclass template.

The simplest usage of the InspectorComponent requires only that you provide a template with your inspector controls:

import {Component} from "@angular/core"
import { InspectorComponent } from "@jsplumbtoolkit/browser-ui-angular"

@Component({
template:`

@if(currentType === 'type1') {
<input jtk-att="name" type="text"></input>
}

@if(currentType === 'type2') {
<input jtk-att="title" type="text"></input>
}
`,
selector:"app-my-inspector"
})
export class MyInspector extends InspectorComponent { }

In this example we've used the underlying currentType to decide what to render. JsPlumb takes care of extracting the data from the backing object and setting it on the input fields, and also committing changes on blur. When you use this in your app the InspectorComponent will, by default, locate the surface to use via the default surface id, so you just need to declare the component without any inputs:

<app-main>
<jsplumb-surface [renderParams]="renderParams"/>
<app-my-inspector/>
</app-main>

currentType and currentObjectType are set by the refresh method, whose default implementation is:

refresh(obj:Base):void {
this.currentType = obj.data.type
this.currentObjectType = obj.objectType
}

Advanced usage

For many usage scenarios the out-of-the-box setup of InspectorComponent is fine, but you may find you need to set something up that is a little more complex. The way to do this is by overriding the refresh method, and also perhaps the reset method. For example, in our Callflow Builder app we allow users to inspect both nodes - to set the text for a text-to-speech node, say, or to specify the number for a call forward, and also ports - on a condition node, the user can choose to branch from one of a set of conditions. To facilitate this we override the refresh method:

portTypeMap:Record<string, string> = {
[TYPE_CONDITIONS]:"condition"
}

refresh(obj: Base) {
if (isPort(obj)) {
this.currentType = this.portTypeMap[obj.getParent().type]
} else {
super.refresh(obj)
}
}

In this example we test the object that is being inspected to see if it is a Node or a Port. If it's a port we assign currentType to "condition". If it's a node we invoke the base refresh method. We're not limited to just manipulating currentType and/or currentObjectType, though - you can do anything you like in the refresh method. If you do set any variables other than the defaults we recommend overriding reset to clean them up:


myService = inject(MyService)

currentColor:string = ''
currentLabel:string = "Label"

refresh(obj: Base) {
this.currentColor = this.myService.calculateColor(obj)
this.currentLabel = this.myService.calculateLabel(obj)
}

reset() {
this.currentColor = ''
this.currentLabel = ''
}

Embedding Angular components

One thing to keep in mind is that you can use any Angular component you like inside of your inspector. For example, in our Angular Callflow Builder app, our inspector template uses an Angular component to manage the variables in a "Set Variables" node:

@Component({
template:`

...

@if(currentType === 'set-variables') {
<div class="jtk-callflow-inspector">
<app-set-values [obj]="currentObj.data"></app-set-values>
</div>
}

...

`,
selector:"app-inspector"
})

Close button

You can instruct JsPlumb to add a close button to your inspectors like this:

<app-main>
<jsplumb-surface [renderParams]="renderParams"/>
<app-my-inspector [showCloseButton]="true"/>
</app-main>

Notification of updates

import { Component } from "@angular/core"
import {Surface } from "@jsplumbtoolkit/browser-ui"

@Component({
template:`<app-main>
<jsplumb-surface [renderParams]="renderParams"/>
<app-my-inspector [afterUpdate]="afterUpdate"/>
</app-main>`,
selector:"my-app"
})
export class MyApp {
afterUpdate(surface:Surface) {
// notification an update occurred
}
}

This can be useful in some advanced scenarios where you want to refresh other parts of your UI in response to an update.


Using the jsPlumb Service

At the core of JsPlumb's Angular integration is the jsPlumbService, which offers access to, and management of, JsPlumb Toolkit instances and surfaces. It is recommended to create an instance of JsPlumb programmatically at the top level of your application, which you can then share between different components. For instance, this is the ngOnInit method of the top level component in the Flowchart Builder demonstration:

export class AppComponent {

@ViewChild(FlowchartComponent) flowchart:FlowchartComponent;
@ViewChild(DatasetComponent) dataset:DatasetComponent;

toolkitId:string;
toolkit:jsPlumbToolkit;

constructor(private $jsplumb: jsPlumbService, private elementRef: ElementRef, private flowchartService: FlowchartService) {
this.toolkitId = this.elementRef.nativeElement.getAttribute('toolkitId');
}

ngOnInit() {
this.toolkit = this.$jsplumb.getToolkit(this.toolkitId, this.toolkitParams)
}

...
}

Retrieving Toolkits and Surfaces

  • getToolkit(id:string, params?:jsPlumbToolkitOptions)

Either retrieves an existing Toolkit with the given ID, or creates a Toolkit instance and returns it. Options for the Toolkit instance may be passed in as the second argument; these are ignored if a Toolkit with the given ID already exists.

  • getSurface(id:string, callback:(surface:Surface)=>void, _params:SurfaceOptions)

Either retrieves the Surface with the given ID, or creates one, with the given Surface options, and returns it. The second argument to this method is a callback function - it is asynchronous, not returning the Surface until it has been initialised fully.

Storing/retrieving data

  • store<T>(key:string, value:T)

Offers a means to store arbitrary data on the service, for retrieval elsewhere in your app.

  • retrieve<T>(key:string):T

Offers a means to retrieve arbitrary data from the service, which you have stored elsewhere in your app.

Working with Shape Libraries

See above for a full discussion of how to work with shape libraries in Angular.

  • registerShapeLibrary<T = ObjectData>(shapeSets:Array<ShapeSet>, key?:string):void

Registers a shape library with the given id

  • retrieveShapeLibrary<T = ObjectData>(key?:string):ShapeLibraryImpl<T>

Retrieves the shape library with the given id

  • exportSvg<T extends ObjectData>(options:AngularSvgExportOptions<T>)

Exports a given surface to an SVG.

  • showSvgExportUI<T extends ObjectData>(options:AngularSvgExportUIOptions<T>)

Shows the Toolkit's SVG export UI

  • exportImage<T extends ObjectData>(options:AngularImageExportOptions<T>, onready:ImageReadyFunction)

Exports a given surface to an image (PNG or JPG)

  • showImageExportUI<T extends ObjectData>(options:AngularImageExportUIOptions<T>)

Shows the Toolkit's image export UI

  • exportPNG<T extends ObjectData>(options:AngularImageExportOptions<T>, onready:ImageReadyFunction)

Exports a given surface to a PNG

  • exportJPG<T extends ObjectData>(options:AngularImageExportOptions<T>, onready:ImageReadyFunction)

Exports a given surface to a JPG

ImageReadyFunction

This function has the type:

type ImageReadyFunction = (params:{width:number, height:number, url:string, el:SVGElement, contentType:string}) => any
  • height Computed height for the image, in pixels.
  • width Computed width for the image, in pixels.
  • url A data url for the image, encoded as base 64.
  • el The underlying SVG element that was exported to the image
  • contentType eg image/png, image/jpeg.

Component List

This is a full list of the components that are available in JsPlumb Angular. For each we provide a sample usage, which does not necessarily use all of the inputs available for the given component. We then provide the full list of available inputs.

SurfaceComponent

The key UI component you'll use to render your UI.

<jsplumb-surface [renderParams]="renderParams" [view]="view"></jsplumb-surface>
Class:SurfaceComponent
Selector: jsplumb-surface
Inputs:
InputTypeDescription
surfaceIdstringID to use for the created surface. If you do not provide this a default value will be used. You only need to provide this if you are rendering a JsPlumb instance to more than one surface.
toolkitIdstringID of the Toolkit to which to attach the surface. If you do not provide this a default value will be used. You only really need to provide this if your page has more than one instance of JsPlumb.
viewAngularViewOptionsOptions for the view - mappings of components and behaviour to model object types.
renderParamsAngularRenderOptionsOptions for the underlying surface widget.
toolkitParamsJsPlumbToolkitOptionsOptional parameters for the underlying Toolkit. These will be used only if the Toolkit instance with the given ID has not yet been created somewhere else in the app.
runInsideAngularbooleanSet this to false if you do not want the Surface to set itself up inside a `runOutsideAngular` call. We do not recommend this. When the Surface setup is run inside Angular all of the event bindings from the surface's pan/zoom/drag handlers are piped through Angular, causing change detection events, which can result in a performance hit. We've only added this in case there is an unforeseen backwards compatibility issue in someone's app, but all of our testing of this change suggests that is unlikely.

MiniviewComponent

Provides a Miniview of the contents of some surface.

Example


import {Component} from "@angular/core"

@Component({
selector:"demo-component",
template:`<div style={{width:"100%",height:"100%"}}>
<jsplumb-surface [data]="data"></jsplumb-surface>
<jsplumb-miniview></jsplumb-miniview>
</div>`
})
export class DemoComponent {
data = { ... }
}

Class:MiniviewComponent
Selector: jsplumb-miniview
Inputs:
InputTypeDescription
surfaceIdstringID of the surface to attach the miniview to. Only required if you have more than one surface in your UI.
elementFilterFunctionOptional filter to determine whether or not include a given vertex in the miniview
typeFunctionFunctionOptional function to determine a `type` for each vertex, which is then written as an attribute onto the DOM element representing the vertex in the miniview.
activeTrackingbooleanWhether or not to move miniview elements at the same time as their related surface element is being dragged. Defaults to true.
clickToCenterbooleanDefaults to true, meaning a click on a node/group in the miniview will cause that node/group to be centered in the related surface.

ControlsComponent

Provides a component that offers a set of controls for some surface.

Example


import {Component} from "@angular/core"

@Component({
selector:"demo-component",
template:`<div style={{width:"100%",height:"100%"}}>
<jsplumb-surface [data]="data"></jsplumb-surface>
<jsplumb-controls></jsplumb-controls>
</div>`
})
export class DemoComponent {
data = { ... }
}

Class:ControlsComponent
Selector: jsplumb-controls
Inputs:
InputTypeDescription
surfaceIdstringID of the surface to attach the controls to. Only required if you have more than one surface in your UI.
undoRedobooleanDefaults to true. Whether or not to show undo/redo buttons.
zoomToExtentsbooleanDefaults to true. Whether or not to show the 'zoom to extents' button
clearbooleanDefaults to true. Whether or not to show the clear button.
clearMessagestringThe message to prompt the user with when they press the clear button. Defaults to 'Clear dataset?'
buttonsControlsComponentButtons[]Optional array of extra buttons to include. See controls component docs.
orientation'row'|'column'Defaults to 'row'.

SurfaceDropComponent

This directive provides a means for you to configure the ability to drag/drop nodes and groups from some part of your UI into the canvas.

Class:SurfaceDropComponent
Selector: [jsplumb-surface-drop]
Inputs:
InputTypeDescription
surfaceIdstringID of the surface to attach to. Only required if you have more than one surface in your UI.
selectorstringCSS3 selector identifying child elements that represent draggable nodes.
dataGeneratorDataGeneratorFunctionFunction to use to generate an initial payload when the user starts to drag something from this component. Optional but you should almost certainly consider providing this, or a `typeGenerator`, since without one of these it is not possible to determine the type of object beind dragged.
typeGeneratorTypeGeneratorFunctionOptional function to use to determine the type of object that a user has just started dragging
groupIdentifierFunctionGroupIdentifierFunctionOptional function to use to determine if the object that is being dragged represents a group. If this function is present, and returns true, then the drag object is considered to be a group. In all other cases the drag object is considered to be a node.
canvasSelectorstringOptional extra selector to use when dragging to identify parts of the surface's canvas that should be considered whitespace. If you use a decorator, for instance, or have otherwise positioned elements on your canvas, these elements will not by default be considered whitespace.
allowDropOnEdgebooleanDefaults to true. Allows items to be dropped onto edges in the canvas.
allowDropOnGroupbooleanDefaults to true. Allows items to be dropped onto groups in the canvas.
allowDropOnCanvasbooleanDefaults to true. Allows items to be dropped onto whitespace.
allowDropOnNodebooleanDefaults to false. Allows items to be dropped onto nodes in the canvas. If this is true and an element is dropped onto a node, the result is the same as if the element has been dropped onto whitespace.

ShapeLibraryPaletteComponent

Provides a palette that renders a shape library, from which new vertices can be dragged onto a surface.

Class:ShapeLibraryPaletteComponent
Selector: jtk-shape-library
Inputs:
InputTypeDescription
shapeLibraryIdstringThe ID of the shape library to render. If not specified, a default will be used, which suffices for applications that are only using one shape library. Defaults to `DEFAULT_SHAPE_LIBRARY_ID`
surfaceIdstringThe ID of the surface to attach to. Only required if you have more than one surface in your ui.
initialSetstringWhen the shape library contains multiple sets, use this to instruct the palette that you want it to initially load just displaying this set. Optional.
dragSizeSizeOptional size to use for dragged elements. Defaults to { w:150, h:100 }
iconSizeSizeOptional size to use for icons. Default to { w:150, h:100 }
fillstringOptional fill color to use for dragged elements. This should be in RGB format, _not_ a color like 'white' or 'cyan' etc.
outlinestringOptional color to use for outline of dragged elements. Should be in RGB format.
showAllMessagestringMessage to use for the 'show all' option in the shape set drop down when there is more than one set of shapes. Defaults to `Show all`.
selectAfterDropbooleanWhen true (which is the default), a newly dropped vertex will be set as the underlying Toolkit's selection.
canvasStrokeWidthnumberStroke width to use in shapes on the canvas. Defaults to 2.
paletteStrokeWidthnumberStroke width to use in shapes in the palette. Defaults to 1.
dataGeneratorDataGeneratorFunctionOptional function you can use to set some initial data for an element being dragged. Note that you cannot override the object's `type` with this function.

ShapeLibraryComponent

Provides a means to render an SVG shape into a component.

Class:ShapeLibraryComponent
Selector: jtk-shape
Inputs:
InputTypeDescription
shapeLibraryIdstringThe ID of the shape library to use. If not specified, a default will be used, which suffices for applications that are only using one shape library.
objObjectDataThe data for the vertex to render. Required.
widthnumberCurrent width for the shape. Although it may seem counterintuitive, this is required - you'll need to set it to `[width]="obj.width"`. Angular's change detection will not pick up changes to this otherwise.
heightnumberCurrent height. See notes for `width`.
showLabelsbooleanWhether or not to show labels.
labelPropertystringDefaults to `label`. The name of the property inside each vertex that contains its label.
labelStrokeWidthstringThe stroke width to use for labels. Optional. Defaults to the Toolkit's default.

InspectorComponent

Provides a base component you can extend to implement an object inspector.

Example

import {Component} from "@angular/core"
import { InspectorComponent } from "@jsplumbtoolkit/browser-ui-angular"

@Component({
template:`

@if(currentType === 'type1') {
<input jtk-att="name" type="text"></input>
}

@if(currentType === 'type2') {
<input jtk-att="title" type="text"></input>
}
`,
selector:"app-my-inspector"
})
export class MyInspector extends InspectorComponent { }
Class:InspectorComponent
Selector: n/a - this component is abstract
Inputs:
InputTypeDescription
showCloseButtonbooleanWhether or not to show a close button on the inspector. Defaults to false.
afterUpdateFunctionOptional callback that will be invoked after the inspector has made an update
surfaceIdstringID of the surface to attach to. If you do not provide this a default value will be used. You only need to provide this if you are rendering a JsPlumb instance to more than one surface.