How to integrate mxGraph with Angular 4?

If anyone still struggling with mxGraph integration in Angular 4/5/6. Then here is Complete Solution:

Few details about different mxGraph Repos:

Repo-1: https://github.com/jgraph/mxgraph
        This is an official release repo of mxGraph. With npm issues.

Repo-2: https://bitbucket.org/jgraph/mxgraph2
        This is an official development repo of mxGraph. With npm issues.

If anyone wants to see what npm issues with these above repos(i.e. Repo-1 and Repo-2), then check these following issues:  
            - https://github.com/jgraph/mxgraph/issues/169
            - https://github.com/jgraph/mxgraph/issues/175

Repo-3: https://bitbucket.org/lgleim/mxgraph2
        Fork of Repo-2. With npm fixes.

Repo-4: https://github.com/ViksYelapale/mxgraph2
        Fork of Repo-2. Merged npm fixes from Repo-3 as well. Added changes(i.e. required for local installation of mxGraph) to this repo.

Steps:

  1. Clone Repo-4. Also, add remote of the official repo(i.e. Repo-2) to take the latest mxGraph updates/release/fixes.

  2. Change directory to the mxgraph2 and run npm install

    $ cd mxgraph2
    $ npm install

  3. Now go to your angular project repo and install mxGraph(i.e. mxgraph2 which we have build locally).

    $ npm install /path/to/mxgraph2

    e.g. npm install /home/user/workspace/mxgraph2

    Which will add a similar entry as below in your package.json file:

    "mxgraph": "file:../mxgraph2"

  4. Run normal npm install once. For adding any missing/dependency package.

    $ npm install

  5. Now we will install mxgraph typings

    Note - Minimum required version of the typescript is 2.4.0

    $ npm install lgleim/mxgraph-typings --save

  6. Now you can use mxGraph in your app.

    i. component.ts

    import { mxgraph } from "mxgraph";
    
    declare var require: any;
    
    const mx = require('mxgraph')({
      mxImageBasePath: 'assets/mxgraph/images',
      mxBasePath: 'assets/mxgraph'
    });
    
    .
    .
    .
    
    ngOnInit() {
       // Note - All mxGraph methods accessible using mx.xyz
       // Eg. mx.mxGraph, mx.mxClient, mx.mxKeyHandler, mx.mxUtils and so on.
    
       // Create graph
    
       var container = document.getElementById('graphContainer');
       var graph = new mx.mxGraph(container);
    
       // You can try demo code given in official doc with above changes.
    }
    

    ii. component.html

    <div id="graphContainer"></div>

  7. That's it !!

Hope it will be helpful.


I also couldn't find any resources that can solve this problem. So I made my own npm packages that others can also use. You can try to use one of these packages for your application.

ts-mxgraph typescript wrapped version of mxgraph library v4.03.
ts-mxgraph-factory typescript wrapper
ts-mxgraph-typings

I used the following method in my project.

Step 1:
create mxgraph.overrides.ts file inside the project

Step 2
Import mxgraph prototype methods. Can also extend the original methods of the library itself.

import '../../assets/deflate/base64.js'
import '../../assets/deflate/pako.min.js';

import { mxgraph, mxgraphFactory } from "ts-mxgraph";
const mx = mxgraphFactory({
    mxBasePath: 'mxgraph',
    mxLoadResources: false,
    mxLoadStylesheets: false,
});

declare const Base64: any;
declare const pako: any;

let mxActor: any = mx.mxActor;
let mxArrow: any = mx.mxArrow;
let mxArrowConnector: any = mx.mxArrowConnector;
let mxGraph: any = mx.mxGraph;
let mxClient: any = mx.mxClient;
let mxClipboard: any = mx.mxClipboard;
let mxCellMarker: any = mx.mxCellMarker;
let mxCodecRegistry: any = mx.mxCodecRegistry;
let mxDoubleEllipse: any = mx.mxDoubleEllipse;
let mxUtils: any = mx.mxUtils;
...
// extends mxgraph prototypes

mxGraph.prototype.updatePageBreaks = function(visible, width, height) {
    const useCssTranforms = this.useCssTransforms, scale = this.view.scale,
        translate = this.view.translate;

    if (useCssTranforms) {
        this.view.scale = 1;
        this.view.translate = new mxPoint(0, 0);
        this.useCssTransforms = false;
    }

    graphUpdatePageBreaks.apply(this, arguments);

    if (useCssTranforms) {
        this.view.scale = scale;
        this.view.translate = translate;
        this.useCssTransforms = true;
    }
}
// Adds panning for the grid with no page view and disabled scrollbars
const mxGraphPanGraph = mxGraph.prototype.panGraph;
mxGraph.prototype.panGraph = function(dx, dy) {
    mxGraphPanGraph.apply(this, arguments);

    if (this.shiftPreview1 != null) {
        let canvas = this.view.canvas;

        if (canvas.ownerSVGElement != null) {
            canvas = canvas.ownerSVGElement;
        }

        const phase = this.gridSize * this.view.scale * this.view.gridSteps;
        const position = -Math.round(phase - mxUtils.mod(this.view.translate.x * this.view.scale + dx, phase)) + 'px ' +
            -Math.round(phase - mxUtils.mod(this.view.translate.y * this.view.scale + dy, phase)) + 'px';
        canvas.style.backgroundPosition = position;
    }
}
...

export {
    mxClient,
    mxUtils,
    mxRubberband,
    mxEventObject,
    mxEdgeHandler,
    mxEvent,
    mxGraph,
    mxGraphModel,
    mxGeometry,
    mxConstants,
    ...
    }

// and then import these where you want to use them

import {
    mxClient,
    mxUtils,
    mxEvent,
    mxGraph,
    mxGraphModel,
    mxGeometry,
    mxConstants,
    mxCell,
    mxDictionary,
    mxCellEditor,
    mxStyleRegistry
} from './mxgraph.overrides';

import { IMAGE_PATH, STYLE_PATH, STENCIL_PATH, urlParams } from '../config';

declare var Base64: any;
declare var pako: any;

export class Graph extends mxGraph {
...
}

I have had the exact same problem. According to the 'lgleim', the problem is with the mxgraph npm package. The issue is discussed here: https://github.com/jgraph/mxgraph/issues/169.

I colud not solve the said problem. However I successfully integrated mxgraph with angular 7 by following this article: https://itnext.io/how-to-integrate-mxgraph-with-angular-6-18c3a2bb8566

Step 1

First of all install latest version of mxgraph:

npm install mxgraph

Step 2

Then download the typings from https://github.com/gooddaytoday/mxgraph-typescript-definitions.git. Extract the file into the 'src' folder of your angular project

Step 3

In your angular.json file, add the following:

  1. In assets array add:

    { "glob": "**/*", "input": "src/assets/", "output": "/assets/" },

    { "glob": "**/*", "input": "./node_modules/mxgraph/javascript/src", "output": "/assets/mxgraph" }

  2. In scripts array add:

    "node_modules/mxgraph/javascript/mxClient.js"

There are two scripts and assets arrays. Once in "build" and once in "test". Add in both.

After doing all that, you are good to go. :)

Example Code:

component.html:

<div #graphContainer id="graphContainer"></div>

component.ts

import {AfterViewInit, Component, ElementRef, ViewChild} from '@angular/core';
declare var mxPerimeter: any;
declare var mxConstants: any;

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

  @ViewChild('graphContainer') graphContainer: ElementRef;
  graph: mxGraph;

  ngAfterViewInit() {
    this.graph = new mxGraph(this.graphContainer.nativeElement);

    // set default styles for graph
    const style = this.graph.getStylesheet().getDefaultVertexStyle();
    style[mxConstants.STYLE_PERIMETER] = mxPerimeter.EllipsePerimeter;
    style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_ELLIPSE;
    style[mxConstants.DEFAULT_VALID_COLOR] = '#00FF00';
    this.graph.getStylesheet().putDefaultVertexStyle (style);

    // add cells
    try {
      const parent = this.graph.getDefaultParent();
      this.graph.getModel().beginUpdate();
      const vertex1 = this.graph.insertVertex(parent, '1', 'Vertex 1', 0, 0, 200, 80);
      const vertex2 = this.graph.insertVertex(parent, '2', 'Vertex 2', 0, 0, 200, 80);
      this.graph.insertEdge(parent, '', '', vertex1, vertex2);
    } finally {
      this.graph.getModel().endUpdate();
      new mxHierarchicalLayout(this.graph).execute(this.graph.getDefaultParent());
    }
  }

}

Note that I have used declare statement to declare mxPerimeter and mxConstants. The reason is that the type definitions are incomplete. Hence I had to declare some of the class names by myself. This just a little hack to avoid compiler errors. By using declare statement I am essentially telling the compiler to allow this class. However it will not help with the intellisense used by various text editor.


This is how i implement the use of mxGraph on Angular. I hope this could help to others.

Important: This didn't works on angular/cli --prod build. You must deactivate the optimization option on angular.json

"production": {
              "outputPath": "dist/PRO",
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              **"optimization": false,**
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
... and so on

First of all install mxgraph and typins from npm as dependency and dev-dependency

npm install mxgrapf --save
npm install @types/mxgraph --save-dev

this must generate both entries into the package.json of the project

"@types/mxgraph": "github:lgleim/mxgraph-typings",
"mxgraph": "4.0.4",

After that i declare in the same file all my needed classes extending the mxgraph this save the cost of declare const mx in all the class that use mxgraph.

The file extending mxgraph classes is something like this:

import { mxgraph } from 'mxgraph'; // Typings only - no code!
declare var require: any;

/**
 *  init mxGraph whith a config object
 */
const mx: typeof mxgraph = require('mxgraph')({
    // mxgraph assets base path
    mxBasePath: 'assets/mxgraph',
    // mxgraph images
    mxImageBasePath: 'assets/mxgraph/images',
    // avoid mxgraph resources load
    mxLoadResources: false,
    mxForceIncludes: false

});

// Objects load in window object
// The original library load, loads object into the window object, this is necesray if you use
// the decode and encode models funcionality of mxgraph. Is necesary that you include all object you 
// use into your models. this is only my case.
window['mxGraphModel'] = mx.mxGraphModel;
window['mxGeometry'] = mx.mxGeometry;
window['MxGeometry'] = mx.mxGeometry;
window['MxPoint'] = mx.mxPoint;
window['mxPoint'] = mx.mxPoint;

/**
 * Into MXUTILITIES exports all the object created by mxgraph as staric properties as we need
 **/
export class MXUTILITIES {
    static mxEvent = mx.mxEvent;
    static mxUtils = mx.mxUtils;
    static mxConstants = mx.mxConstants;
    static mxStencilRegistry = mx.mxStencilRegistry;
    static mxPerimeter = mx.mxPerimeter;
    static mxEdgeStyle = mx.mxEdgeStyle;
    static mxEffects = mx.mxEffects;
    static mxClient = mx.mxClient;
    static mxCodecRegistry = mx.mxCodecRegistry;

}

/**
 * Exports for all classes we need extending mxgrah, you can extend, overwrite methods and so on
 * 
 */
export class MxGraphModel extends mx.mxGraphModel {}
export class MxOutline extends mx.mxOutline { }
export class MxKeyHandler extends mx.mxKeyHandler { }
export class MxCompactTreeLayout extends mx.mxCompactTreeLayout { }
export class MxLayoutManager extends mx.mxLayoutManager { }
export class MxDivResizer extends mx.mxDivResizer { }
export class MxCellOverlay extends mx.mxCellOverlay { }
export class MxImage extends mx.mxImage { }
export class MxEdgeHandler extends mx.mxEdgeHandler { }
export class MxPrintPreview extends mx.mxPrintPreview { }
export class MxWindow extends mx.mxWindow { }
export class MxGraphView extends mx.mxGraphView { }
export class MxGraphHandler extends mx.mxGraphHandler { }
export class MxGraphSelectionModel extends mx.mxGraphSelectionModel { }
export class MxToolbar extends mx.mxToolbar { }
export class MxEventObject extends mx.mxEventObject { }
export class MxCodec extends mx.mxCodec { }
export class MxObjectCodec extends mx.mxObjectCodec { }
export class MxFastOrganicLayout extends mx.mxFastOrganicLayout { }
export class MxGeometry extends mx.mxGeometry { }
export class MxHierarchicalLayout extends mx.mxHierarchicalLayout { }
export class MxStencil extends mx.mxStencil { }
export class MxRubberband extends mx.mxRubberband { }
export class MxCellRenderer extends mx.mxCellRenderer { }
export class MxPoint extends mx.mxPoint { }
export class MxConnector extends mx.mxConnector { }
export class MxLine extends mx.mxLine { }
export class MxArrowConnector extends mx.mxArrowConnector { }
export class MxCell extends mx.mxCell {}
export class MxGraph extends mx.mxGraph {}

To create a new graph I use a service that store graphs generated and notify selected cells and new graph created to all subcribed components

import { Injectable, ElementRef } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { BehaviorSubject, Subject } from 'rxjs';
import { MxCell, MxGraph, MxEventObject, MXUTILITIES, MxGraphSelectionModel } from '../classes/mxgraph.class';


@Injectable({ providedIn: 'root' })
export class GraphsService { 

  private graphsSubject: BehaviorSubject<MxGraph[]> = new BehaviorSubject([]);
  private graphs$: Observable<MxGraph[]>;
  private graphs: MxGraph[] = [];
  
  private selectedCellsSubject: BehaviorSubject<MxCell[]> = new BehaviorSubject([]);
  private selectedCells$: Observable<MxCell[]>;
  private selectedCells: MxCell[];
  
  constructor() {
    this.graphs$ = this.graphsSubject.asObservable();
    this.stamp = Date.now();
  }
 
 /**
   * Generate a new graph into the received container
   *
   * @memberOf GraphsService
   */
  newGraph(graphContainer: ElementRef, name?: string): MxGraph {
    const newGraph: MxGraph = this.initNewGraph(graphContainer, name);
    this.graphs.push(newGraph);
    this.graphsSubject.next(this.graphs);
    return newGraph;
  }

  /**
   * Init new graph
   *
   * @memberOf GraphsService
   */
  private initNewGraph(graphContainer: ElementRef, name: string) {
    let newGraph: MxGraph;
    newGraph = new MxGraph(graphContainer.nativeElement);
    if (!name) name = 'Nuevo gráfico';
    newGraph.getModel().getRoot().setValue(name);
    newGraph.setConnectable(true);
    newGraph.setMultigraph(false);
    newGraph.selectionModel.addListener(MXUTILITIES.mxEvent.CHANGE, (mxGraphSelectionModel: MxGraphSelectionModel, evt: MxEventObject) => {
      this.emitSelectedCell(mxGraphSelectionModel.cells as MxCell[]);

    });

    return newGraph;
  }
  
  /**
   * Emits the selected cells from the graph
   * @memberOf GraphsService
   */
  private emitSelectedCell(cells: MxCell[]) {

    if (!cells) this.selectedCells = [];
    else this.selectedCells = cells;
    this.selectedCellsSubject.next(this.selectedCells);
  }

  
  }