DEMOS
DOCS
FEATURES
DOWNLOAD
PURCHASE
CONTACT
BLOG

Angular Integration

The jsPlumb Toolkit has a service and several components to assist you in integrating with Angular (6.x, 5.x, 4.x and 2.x - for Angular 1.x integration, refer to this page ). The default, and recommended, language for writing applications in Angular is Typescript. jsPlumb's integration code is written in Typescript.

Imports

package.json

Add these lines to your package.json:

"dependencies": {
    ...
    "jsplumbtoolkit":"file:./somewhere/jsplumbtoolkit.tgz",
    "jsplumbtoolkit-angular":"file:./somewhere/jsplumbtoolkit-angular.tgz"
    ...
},

Setup

import { jsPlumbToolkitModule } from "jsplumbtoolkit-angular";

...

@NgModule({
    imports:      [ BrowserModule, jsPlumbToolkitModule ],
    declarations: [ AppComponent, QuestionNodeComponent, ActionNodeComponent, StartNodeComponent, OutputNodeComponent ],
    bootstrap:    [ AppComponent ],
    entryComponents: [ QuestionNodeComponent, ActionNodeComponent, StartNodeComponent, OutputNodeComponent ],
    schemas:[ CUSTOM_ELEMENTS_SCHEMA ]
})

This example comes from the Angular demonstration that ships with the Toolkit. Note that you need to import the CUSTOM_ELEMENTS_SCHEMA schema.

Component imports

To import components inside a .ts file:

import { jsPlumbSurfaceComponent, jsPlumbMiniviewComponent, jsPlumbPaletteComponent } from "jsplumbtoolkit-angular";

jsPlumb Service

At the core of jsPlumb's Angular integration is the jsPlumb Service, which offers access to, and management of, Toolkit instances and Surfaces. It is recommended to create an instance of the Toolkit 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 Angular demo that ships with the Toolkit:

export class AppComponent {

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

  toolkitId:string;
  toolkit:jsPlumbToolkit;

  constructor(private $jsplumb:jsPlumbService) {
    this.toolkitId = "flowchart";
  }

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

  ...
}

Methods

The jsPlumb Service offers several methods. getToolkit and getSurface are the methods client applications will mostly use; unless you write your own component that renders a Surface you won't need to use the methods that add/remove 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.

  • addSurface(id:string, surface:Surface)

Registers a Surface with the service. This method is used by the jsplumb-surface component that ships with the Toolkit.

  • removeSurface(id:string, surface:Surface)

Unregisters a Surface from the service, also calling destroy on the Surface (which calls destroy on any attached Miniviews). This method is used by the jsplumb-surface component that ships with the Toolkit.

  • addMiniview(surfaceId:string, params:MiniviewOptions)

Used by the jsplumb-miniview component.

TOP


jsPlumb Components

Angular is component based. The Toolkit offers 4 components:

jsplumb-surface

This is an element component that provides a Surface widget to render the contents of some Toolkit instance.

<jsplumb-surface jtkId="toolkit" surfaceId="surfaceId" [renderParams]="anObjectReference" [view]="anObjectReference" [nodeResolver]="aFunctionReference">
</jsplumb-surface>
Attributes

All attributes are optional except nodeResolver and jtkId.

  • jtkId ID of the Toolkit instance to render.
  • surfaceId Unique ID for the Surface widget. Required if you wish to attach a Miniview or a Palette.
  • renderParams Parameters to pass in to the constructor of the Surface widget.
  • view View parameters. Views are discussed here.

jsplumb-miniview

This is an element component that provides a Miniview that can be attached to some Surface.

<jsplumb-miniview surfaceId="surfaceId"></jsplumb-miniview>
Attributes
  • surfaceId ID for the Surface widget to which to attach the Miniview.

jsplumb-palette

This is an "attribute component": it is created by adding this attribute to some other element. The Palette component provides a means to implement drag/drop of new Nodes/Groups onto your Surface.

<div class="someClass" jsplumb-palette selector="li" surfaceId="surfaceId" 
        [typeExtractor]="typeExtractor" [dataGenerator]="dataGenerator">
    <ul>
        <li data-node-type="foo">FOO</li>
        <li data-node-type="bar">BAR</li>
    </ul>
</div>
Attributes
  • selector A valid CSS3 selector identifying descendant nodes that are to be configured as draggable/droppables.
  • surfaceId The ID of the Surface to which to attach the Palette.
  • typeExtractor A Function that, given some DOM element, can return the type of the Node/Group the element represents. In this example, our typeExtractor function would return the value of the data-node-type attribute.
  • dataGenerator This optional Function can be used to provide default data for some Node/Group type.

For further reading on the concept of Palettes, see this page .

TOP

Rendering Nodes and Groups

Each Node or Group in your UI is rendered as an individual component. These component definitions need to be included in the declarations and entryComponents members of your application's module definition.

Definition

As an example, consider the component we use to render an Action node in the Flowchart builder demonstration:

@Component({ templateUrl:"templates/action.html" })
export class ActionNodeComponent extends BaseEditableNodeComponent  { }

Here, BaseEditableNodeComponent is a class that declares a couple of common editing methods for that specific demonstration. But there is a key piece in the declaration of BaseEditableNodeComponent that you must take into account:

class BaseEditableNodeComponent extends BaseNodeComponent {

}

... the fact that it extends BaseNodeComponent. Your components must extend BaseNodeComponent.

Template

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

<div [style.width]="obj.w + 'px'" [style.height]="obj.h +'px'" class="flowchart-object flowchart-action">
    <div>
        <div class="node-edit node-action" (click)="editNode(obj)">
            <i class="fa fa-pencil-square-o"></i>
        </div>
        <div class="node-delete node-action" (click)="removeNode(obj)">
            <i class="fa fa-times"></i>
        </div>
        <svg [attr.width]="obj.w" [attr.height]="obj.h">
            <rect [attr.x]="0" [attr.y]="0" [attr.width]="obj.w" [attr.height]="obj.h" class="outer"/>
            <rect [attr.x]="10" [attr.y]="10" [attr.width]="obj.w-20" [attr.height]="obj.h-20" class="inner"/>
            <text text-anchor="middle" [attr.x]="obj.w/2" [attr.y]="obj.h/2" dominant-baseline="central"></text>
        </svg>
    </div>
    <jtk-target port-type="target"></jtk-target>
    <jtk-source port-type="source" filter=".outer"></jtk-source>
</div>

It's a standard Angular template. The main thing to be aware of here is that the backing data for the Node or Group is presented as the member obj to the template. You don't, of course, need to use the templateUrl approach - you can provision the template in any way that Angular supports, including directly as a multi-line string via the template parameter.

Mapping to a type

You map components to node/group types in the view. Here's the nodes section from the view in the Angular Flowchart Builder application:

view = {
    nodes:{
      "start":{
        component:StartNodeComponent
      },
      "selectable": {
        events: {
          tap: (params:any) => {
            this.toggleSelection(params.node);
          }
        }
      },
      "question":{
        parent:"selectable",
        component:QuestionNodeComponent
      },
      "output":{
        parent:"selectable",
        component:OutputNodeComponent
      },
      "action":{
        parent:"selectable",
        component:ActionNodeComponent
      }
    },
    edges: {

    ...
    }
}

In previous versions of the Toolkit's Angular integration it was necessary to map component names to actual components via a nodeResolver function, but from 1.11.0 this is no longer necessary.

Declaration

You must declare each Node/Group component in both the declarations and entryComponents members of your module definition. Here's the module definition for the Flowchart builder demonstration, for example:

import { jsPlumbToolkitModule } from "jsplumbtoolkit-angular";

...

@NgModule({
    imports:      [ BrowserModule, jsPlumbToolkitModule ],
    declarations: [ AppComponent, QuestionNodeComponent, ActionNodeComponent, StartNodeComponent, OutputNodeComponent ],
    bootstrap:    [ AppComponent ],
    entryComponents: [ QuestionNodeComponent, ActionNodeComponent, StartNodeComponent, OutputNodeComponent ],
    schemas:[ CUSTOM_ELEMENTS_SCHEMA ]
})

Checklist

  • Each of your components extends BaseNodeComponent from the Toolkit's Angular integration
  • 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
  • Each of your components is declared in the entryComponents list in your module definition
  • You've mapped each expected node/group type to a component in your view

TOP


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 Database Visualizer, 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-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:

<li class="table-column table-column-type-" data-primary-key="obj.primaryKey" data-port-id="obj.id">
  <div class="table-column-edit" (click)="editName()">
    <i class="fa fa-pencil table-column-edit-icon"></i>
  </div>
  <div class="table-column-delete" (click)="remove()">
    <i class="fa fa-times table-column-delete-icon"></i>
  </div>
  <div><span></span></div>
  <!--
      configure the li as an edge source, with a type of column, a scope derived from
      the columns datatype, and a filter that prevents dragging new edges from the delete button or from the label.
  -->
  <jtk-source port-id="" scope="" filter=".table-column-delete, .table-column-delete-icon, span, .table-column-edit, .table-column-edit-icon" filter-exclude="true"></jtk-source>
  <!--
      configure the li as an edge target, with a type of column, and a scope derived from the
      column's datatype.
  -->
  <jtk-target port-id="" scope=""></jtk-target>
</li>

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 both the declarations and entryComponents members 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 ],
    entryComponents: [ TableNodeComponent, ColumnComponent, ViewNodeComponent ],
    schemas:[ CUSTOM_ELEMENTS_SCHEMA ]
})

...

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

Checklist

  • Each of your port components extends BasePortComponent from the Toolkit's Angular integration
  • 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
  • Each of your components is declared in the entryComponents list in your module definition
  • You've mapped each expected port to a component in your view

TOP


Integration with an existing application

This is a rough guide to integrating the Toolkit into an existing Angular application.

1. Copy the toolkit imports (toolkit + toolkit angular) from the Angular demo into your package.json, and get them installed.
"dependencies":{

  ...
  "jsplumbtoolkit":"file:path/to/jsplumbtoolkit.tgz",
  "jsplumbtoolkit-angular":"file:path/to/jsplumbtoolkit-angular.tgz",
  ...
}  
2. Import the jsplumbtoolkit-defaults.css file into your CSS. This provides some sane defaults.

The default in an Angular CLI app is to use the styles.css file to orchestrate your css. We import it like this in our demo pages:

@import "jsplumbtoolkit-defaults.css";
3. Figure out where in your app you want to create an instance of the Toolkit.

You create instances of the Toolkit via the jsPlumb service. For instance, in our Angular demo, we do this in the ngInit method of our AppComponent (the main component in the demo), after injecting the service:

export class AppComponent {

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

  toolkitId:string;
  toolkit:jsPlumbToolkit;

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

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

...
}

Note here that we read toolkitId from the DOM element but you could fix that to some string. You'll need this Toolkit ID when you configure your Surface element below.

4. Create an empty component - and its template - that extends BaseNodeComponent:
@Component({ templateUrl:"templates/simple.html" })
export class SimpleNodeComponent extends BaseNodeComponent { }

This is about as simple a component as you can make; it has no code in it at all. But it is important that it extends BaseNodeComponent (which is part of the Toolkit's Angular integration).

Here's a basic template you could use (this would be in the file ./templates/simple.html according to the component declaration above):

<div style="width:100px;height:50px;outline:1px solid;">
  <h3></h3>
</div>

Note the underlying data is presented to an Angular template as obj. This is always the case.

5. Add the jsplumb-surface element to some component...
<jsplumb-surface [surfaceId]="surfaceId" [toolkitId]="toolkitId" [view]="view" [renderParams]="renderParams"></jsplumb-surface>

...and in the component provide a few things:

...


view:{
  nodes:{
    default:{
      component:SimpleNodeComponent
    }
  }
}

renderParams:{
  layout:{
    type:"Spring"
  }
}
  • view is the declarative mapping of definitions to rendering and behaviour, discussed in our documentation. This is a very simple view - it just maps a renderer for all node types.

  • renderParams are the parameters ultimately passed to the render call on the underlying Toolkit instance. These are the parameters for the Surface widget. Here we just set a layout, but there’s []a lot you can do with this](rendering).

6. Once you have done these steps, you should be able to load some data:
toolkit.load({
  nodes:[ {id:"1", name:"one"}, {id:"2", name:"two"}],
  edges:[ {source:"1", target:"2" } ]
})

Further Reading

We refer you to the Angular Demonstration and Angular Database Visualizer documentation.

TOP


Supported Versions

All versions of Angular from 2.x.x through 6.x.x are supported. Our demonstration page imports Angular 6 (and Angular CLI 6), but if you wish to use an earlier version, here we provide example imports for 5,4 and 2. Note also that in Angular CLI 6, the file .angular-cli.json was renamed to angular.json.

5.x.x

{
  "name": "jsplumb-toolkit-angular",
  "version": "x.x.x",
  "license": "Commercial",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "prod-build": "ng build --prod --base-href .",
    "tscr": "tsc -traceResolution",
    "tsc": "tsc"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "^5.0.0",
    "@angular/compiler": "^5.0.0",
    "@angular/core": "^5.0.0",
    "@angular/forms": "^5.0.0",
    "@angular/http": "^5.0.0",
    "@angular/platform-browser": "^5.0.0",
    "@angular/platform-browser-dynamic": "^5.0.0",
    "@angular/router": "^5.0.0",
    "core-js": "^2.4.1",
    "rxjs": "^5.1.0",
    "zone.js": "^0.8.5",
    "jsplumbtoolkit": "file:../../jsplumbtoolkit.tgz",
    "jsplumbtoolkit-angular": "file:../../jsplumbtoolkit-angular.tgz",
    "jsplumbtoolkit-undo-redo": "file:../../jsplumbtoolkit-undo-redo.tgz"
  },
  "devDependencies": {
    "@angular-devkit/core": "0.0.29",
    "@angular/cli": "1.5.0",
    "@angular/compiler-cli": "^5.0.0",
    "@types/node": "~6.0.60",
    "ts-node": "~2.0.0",
    "tslint": "~4.4.2",
    "typescript": "~2.4.2"
  }
}

4.x.x

{
  "name": "jsplumb-toolkit-angular",
  "version": "x.x.x",
  "license": "Commercial",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "prod-build": "ng build --prod --base-href .",
    "tscr": "tsc -traceResolution",
    "tsc": "tsc"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "^4.0.0",
    "@angular/compiler": "^4.0.0",
    "@angular/core": "^4.0.0",
    "@angular/forms": "^4.0.0",
    "@angular/http": "^4.0.0",
    "@angular/platform-browser": "^4.0.0",
    "@angular/platform-browser-dynamic": "^4.0.0",
    "@angular/router": "^4.0.0",
    "core-js": "^2.4.1",
    "jsplumbtoolkit": "file:../../jsplumbtoolkit.tgz",
    "jsplumbtoolkit-angular": "file:../../jsplumbtoolkit-angular.tgz",
    "jsplumbtoolkit-undo-redo": "file:../../jsplumbtoolkit-undo-redo.tgz",
    "rxjs": "^5.1.0",
    "zone.js": "^0.8.5"
  },
  "devDependencies": {
    "@angular/cli": "1.3.0",
    "@angular/compiler-cli": "^4.0.0",
    "@types/node": "~6.0.60",
    "ts-node": "~2.0.0",
    "tslint": "~4.4.2",
    "typescript": "~2.4.2"
  }
}

2.x.x

{
  "name": "jsplumb-toolkit-angular",
  "version": "x.x.x",
  "license": "Commercial",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "prod-build": "ng build --prod --base-href .",
    "tscr": "tsc -traceResolution",
    "tsc": "tsc"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "^2.4.0",
    "@angular/compiler": "^2.4.0",
    "@angular/core": "^2.4.0",
    "@angular/forms": "^2.4.0",
    "@angular/http": "^2.4.0",
    "@angular/platform-browser": "^2.4.0",
    "@angular/platform-browser-dynamic": "^2.4.0",
    "@angular/router": "^3.4.0",
    "core-js": "^2.4.1",
    "rxjs": "^5.1.0",
    "zone.js": "^0.7.2",
    "jsplumbtoolkit": "file:../../jsplumbtoolkit.tgz",
    "jsplumbtoolkit-angular": "file:../../jsplumbtoolkit-angular.tgz",
    "jsplumbtoolkit-undo-redo": "file:../../jsplumbtoolkit-undo-redo.tgz"
  },
  "devDependencies": {
    "@angular/cli": "1.3.0",
    "@angular/compiler-cli": "^2.4.0",
    "@types/node": "~6.0.60",
    "ts-node": "~2.0.0",
    "tslint": "~4.4.2",
    "typescript": "~2.4.2"
  }
}