Angular example app with TypeScript and SCSS

JointJS+ is a standard JavaScript library that you can use in any JavaScript framework. In this tutorial, we will show how you can use JointJS+ in your Angular apps. The tutorial will go over the typical process of creating an Angular app, setting up the build process, and integrating JointJS+ components into your Angular app. The resulting very simple demo app will display the JointJS+'s Paper and PaperScroller components, and render a rectangle element to that paper area. Similarly, you can add any other JointJS+ UI components and build up a fully-featured diagramming application. Let's go!

The source code for the following tutorial can be found here. Looking for a different version of your favourite framework? Be sure to checkout the different branches of the repository to see if you find what you need.

Prerequisites

Create an Angular project

Let's create a new project from the Angular CLI. Simply run:

ng new angular-rappid

You will be prompted to choose a preset and a few options:

After a couple of seconds, a new Angular project should be created. Get started with the following commands:

cd angular-rappid
ng serve

The app should be ready and running at http://localhost:4200/

Add JointJS+ to the Angular project

Firstly, we need to add JointJS+ to our dependencies, and as we are using TypeScript, we will also add some "@types" to our devDependencies. For this tutorial, we will place our installable "rappid.tgz" package in the root directory of our Angular app. After that is completed, we can update our existing dependencies and devDependencies in our package.json file with the following:

"dependencies": {
    ...
    "@clientio/rappid": "file:rappid.tgz", // path to file
    ...
},
"devDependencies": {
    ...
    "@types/backbone": "latest", // "latest" value will install the latest package version
    "@types/jquery": "latest",
    ...
}

Then, run npm install.

Next, open the "tsconfig.json" file in the root project directory, and in the "compilerOptions", set "strictPropertyInitialization" to false, and "skipLibCheck" to "true" as there is no need for the TS compiler to check the JointJS+ library:

"compilerOptions": {
    ...
    "strictPropertyInitialization": false,
    "skipLibCheck": true,
    ...
},

Then, go to the "tsconfig.app.json" file and add this line to the "files" section:

"files": [
    ...
    "node_modules/@clientio/rappid/index.ts",
    ...
]

To utilize testing with JointJS+ and Angular, you can also add the same line to "tsconfig.spec.json". Some of the pre-defined tests created with Angular CLI have been removed from the tutorial repository, so be sure to update your own tests if that is a requirement for you.

"files": [
    ...
    "node_modules/@clientio/rappid/index.ts",
    ...
]

Specific Cases

When working with DirectedGraphs, you need to add some additional dependencies to your app. Additionally, if you would like to overwrite any dependencies used in JointJS+, you can add a specific package version.

"dependencies": {
    ...
    "@clientio/rappid": "file:rappid.tgz",
    "dagre": "latest", // install latest package version
    "graphlib": "latest",
    "lodash": "4.17.11", // use specific package version
    ...
},

"devDependencies": {
    ...
    "@types/dagre": "latest",
    "@types/graphlib": "latest",
    ...
},

Build your app

Before we start, let's clean up our project a little bit:

Now, let's go to the "app.component.html", and add our canvas container. First, add an empty "div" with a class of "canvas", and a template reference equal to "#canvas". A template reference is an Angular instance property that will allow us to access an HTML element from the script. More information on template reference can be found here: Template reference variables

So now our template - "app.component.html" will look like this:

<div class="canvas" #canvas></div>

It's time to add some basic styling. Go to "styles.scss" and replace it with the code below:

@import "~@clientio/rappid/rappid.css";

body {
  height: 100vh;
  box-sizing: border-box;
  margin: 0;
}

Notice the import of the JointJS+ stylesheet at the top of the section. The prefix "~" at the start of the path instructs the Webpack loader to resolve the import from a node module path.

Now, go to "app.component.scss" and add the code below:

.canvas {
    width: 100%;
    height: 100%;

    .joint-paper {
        border: 1px solid #A0A0A0;
    }
}

In "app.component.ts", import the JointJS+ dia, ui and shapes namespaces.

import { dia, ui, shapes } from '@clientio/rappid';

After that, inside the component class add:

@ViewChild('canvas') canvas: ElementRef;

private graph: dia.Graph;
private paper: dia.Paper;
private scroller: ui.PaperScroller;

Now, it's time to create our JointJS+ components.

First of all, remember to import ViewChild and ElementRef. Now, let's add an Angular lifecycle method called "ngOnInit" - don't forget to import the "OnInit" interface and implement it in your class! More information about Angular lifecycle methods can be found here: Lifecycle hooks

Below you can find the snippet of how "app.component.ts" should look by now:

import { OnInit, Component, ElementRef, ViewChild } from '@angular/core';
import { dia, ui, shapes } from '@clientio/rappid';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {

  @ViewChild('canvas') canvas: ElementRef;

  private graph: dia.Graph;
  private paper: dia.Paper;
  private scroller: ui.PaperScroller;
 
  public ngOnInit(): void {
    
  }
}

Inside the "ngOnInit" method add Graph, Paper, and PaperScroller like this:

const graph = this.graph = new dia.Graph({}, { cellNamespace: shapes });

const paper = this.paper = new dia.Paper({
    model: graph,
    background: {
        color: '#F8F9FA',
    },
    frozen: true,
    async: true,
    sorting: dia.Paper.sorting.APPROX,
    cellViewNamespace: shapes
});

const scroller = this.scroller = new ui.PaperScroller({
    paper,
    autoResizePaper: true,
    cursor: 'grab'
});

Resources:

Now, let's render our PaperScroller component, create a simple rectangle shape, and add it to our graph. Add the following code inside the "ngOnInit" method:

scroller.render();

const rect = new shapes.standard.Rectangle({
    position: { x: 100, y: 100 },
    size: { width: 100, height: 50 },
    attrs: {
        label: {
            text: 'Hello World'
        }
    }
});

this.graph.addCell(rect);

Next, we can use another lifecycle method called “ngAfterViewInit” (also remember to import the “AfterViewInit” interface and implement it in the class). Add it after the “ngOnInit” method:

import { AfterViewInit, OnInit, Component, ElementRef, ViewChild  } from '@angular/core';
...
export class AppComponent implements OnInit, AfterViewInit {
...
    public ngAfterViewInit(): void {

    }
}

Inside the "ngAfterViewInit" method, we are able to use our "refs" to work with the DOM elements. Append the created PaperScroller component, center it, and also unfreeze the Paper.

const { scroller, paper, canvas } = this;
canvas.nativeElement.appendChild(this.scroller.el);
scroller.center();
paper.unfreeze();

The final code in "app.component.ts" should look like the following:

import { AfterViewInit, OnInit, Component, ElementRef, ViewChild  } from '@angular/core';
import { dia, ui, shapes } from '@clientio/rappid';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, AfterViewInit {
    @ViewChild('canvas') canvas: ElementRef;

    private graph: dia.Graph;
    private paper: dia.Paper;
    private scroller: ui.PaperScroller;

    public ngOnInit(): void {
        const graph = this.graph = new dia.Graph({}, { cellNamespace: shapes });

        const paper = this.paper = new dia.Paper({
            model: graph,
            background: {
                color: '#F8F9FA',
            },
            frozen: true,
            async: true,
            sorting: dia.Paper.sorting.APPROX,
            cellViewNamespace: shapes
        });

        const scroller = this.scroller = new ui.PaperScroller({
            paper,
            autoResizePaper: true,
            cursor: 'grab'
        });

        scroller.render();

        const rect = new shapes.standard.Rectangle({
            position: { x: 100, y: 100 },
            size: { width: 100, height: 50 },
            attrs: {
                label: {
                    text: 'Hello World'
                }
            }
        });

        this.graph.addCell(rect);
    }

    public ngAfterViewInit(): void {
        const { scroller, paper, canvas } = this;
        canvas.nativeElement.appendChild(this.scroller.el);
        scroller.center();
        paper.unfreeze();
    }
}
    

Everything is ready! Now run npm start to host your application. Your demo app should look like this:

exampleApp