Angular 5/6 and the bpmn-js-properties-panel


#1

Has anyone had any luck getting the “camunda” bpmn-js-properties-panel to work properly in an Angular 5/6 application? I’ve seen various projects out there where they make use of a custom panel (which kind of skirts the issue) however I am more interested in replicating exactly what is in the Javascript bpmn-js-properties-panel example. In the Javascript bpmn-js-properties-panel example when you select a service task you see a panel that contains various property groups which relate to the type of service:

bpmn-example

Using the same imports and the same calls (except in TypeScript) the Angular version of the panel looks like:

bpmn-modeler

Notice that property groups that correspond to the selected task type such as “Details” where the implementation is set and “Asynchronous Continuation” are missing. Also, the CSS looks different.

Further, when inspecting the DOM of the Javascript bpmn-js-properties-panel example you see the elements corresponding to the type of task:

<div class="bpp-properties-group" data-group="details">
   <span class="group-toggle"></span><span class="group-label">Details</span>
   <div class="bpp-properties-entry bpp-dropdown" data-entry="implementation">
      <label for="camunda-implementation">Implementation</label>
      <select id="camunda-implementation-select" name="implType" data-value="">
         <option value="class">Java Class</option>
         <option value="expression">Expression</option>
         <option value="delegateExpression">Delegate Expression</option>
         <option value="external">External</option>
         <option value="connector">Connector</option>
         <option value=""></option>
      </select>
   </div>
   <div class="bpp-properties-entry bpp-textfield" data-entry="delegate">
      <label for="camunda-delegate" data-show="isHidden" data-value="delegationLabel" class="bpp-hidden"></label>
      <div class="bpp-field-wrapper bpp-hidden" data-show="isHidden"><input id="camunda-delegate" type="text" name="delegate" data-show="isHidden" class="bpp-hidden"><button class="clear bpp-hidden" data-action="clear" data-show="canClear"><span>X</span></button></div>
   </div>
   <div class="bpp-properties-entry bpp-textfield" data-entry="resultVariable">
      <label for="camunda-resultVariable" data-show="isHidden" class="bpp-hidden">Result Variable</label>
      <div class="bpp-field-wrapper bpp-hidden" data-show="isHidden"><input id="camunda-resultVariable" type="text" name="resultVariable" data-show="isHidden" class="bpp-hidden"><button class="clear bpp-hidden" data-action="clear" data-show="canClear"><span>X</span></button></div>
   </div>
   <div class="bpp-properties-entry bpp-textfield" data-entry="externalTopic">
      <label for="camunda-externalTopic" data-show="isHidden" class="bpp-hidden">Topic</label>
      <div class="bpp-field-wrapper bpp-hidden" data-show="isHidden"><input id="camunda-externalTopic" type="text" name="externalTopic" data-show="isHidden" class="bpp-hidden"><button class="clear bpp-hidden" data-action="clear" data-show="canClear"><span>X</span></button></div>
   </div>
   <div class="bpp-properties-entry" data-entry="configureConnectorLink"><a data-action="linkSelected" data-show="hideLink" class="bpp-entry-link bpp-hidden"></a></div>
</div>

When looking at the Angular version you can pinpoint where the property element should be, but aren’t – as well as visibility settings set to hidden:

<div class="bpp-properties-group bpp-hidden" data-group="details">
    <span class="group-toggle"></span>
    <span class="group-label">Details</span> 
</div>

Without knowing too much about the library my assumption is that the CSS or the moddle isn’t being loaded properly into my Angular 6 application. I’ve tried various differing things to see if I could make it work but haven’t had much luck. My thought is that I could show what I already have in place and hopefully something who’s gone through this already can pinpoint where I went wrong.

app.component.ts

import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';

import Modeler from 'bpmn-js/lib/Modeler.js';
import propertiesPanelModule from 'bpmn-js-properties-panel';
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda';
import * as camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda.json';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'Workflow Modeler';
  modeler: Modeler;

  @ViewChild('canvas')
  private canvesRef: ElementRef;

  constructor(private http: HttpClient) {
  }

  ngOnInit(): void {
    this.modeler = new Modeler({
      container: '#canvas',
      width: '100%',
      height: '600px',
      propertiesPanel: {
        parent: '#properties'
      },
      additionalModules: [
        propertiesPanelModule,
        propertiesProviderModule
      ],
      moddleExtensions: {
        camunda: camundaModdleDescriptor
      }
    });
    this.load();
  }

  load(): void {
    this.getExample().subscribe(data => {
      this.modeler.importXML(data, value => this.handleError(value));
    });
  }

  handleError(err: any) {
    if (err) {
      console.warn('Ups, error: ', err);
    }
  }

  public getExample(): Observable<string> {
    const url = '/assets/bpmn/initial.bpmn'; // local
    return this.http.get(url, {responseType: 'text'});
  }

}

app.component.html

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

<button (click)="load()">Load <i class="fa fa-folder-open"></i></button>

<div class="modeler">
  <div id="canvas" #canvas></div>
  <div class="properties-panel" id="properties"></div>
</div>

app.component.css

.properties-panel {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: 260px;
  z-index: 10;
  border-left: 1px solid #ccc;
  overflow: auto;
}

.properties-panel:empty {
  display: none;
}

.properties-panel > .djs-properties-panel {
  padding-bottom: 70px;
  min-height: 100%;
}

Some of the smaller changes I’ve made is adding “(window as any).global = window;” to my polyfills.ts document as well as adding support for JSON files in my typings.d.ts:

declare module '*.json' {
  const value: any;
  export default value;
}

As for importing the stylesheets from the various underlying projects I’ve tried multiple things in the angular.json document:

"styles": [
    "src/styles.css",
    "node_modules/bpmn-js/dist/assets/diagram-js.css",
    "node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css",
    "node_modules/bpmn-js-properties-panel/styles/properties.less"
],

as well as pulling the styles from the installed node modules:

"styles": [
    "src/styles.css",
    "node_modules/diagram-js/assets/diagram-js.css",
    "node_modules/bpmn-font/dist/css/bpmn-embedded.css",
    "node_modules/bpmn-js-properties-panel/styles/properties.less"
],

And, just for full disclosure, here is the package.json

{
  "name": "workflow-modeler",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "prod": "ng build --base-href /workflow-modeler/"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^6.1.0",
    "@angular/common": "^6.1.0",
    "@angular/compiler": "^6.1.0",
    "@angular/core": "^6.1.0",
    "@angular/forms": "^6.1.0",
    "@angular/http": "^6.1.0",
    "@angular/platform-browser": "^6.1.0",
    "@angular/platform-browser-dynamic": "^6.1.0",
    "@angular/router": "^6.1.0",
    "bpmn-js": "^2.5.2",
    "bpmn-js-properties-panel": "^0.26.2",
    "camunda-bpmn-moddle": "^3.0.0",
    "core-js": "^2.5.4",
    "diagram-js": "^2.6.1",
    "rxjs": "~6.2.0",
    "zone.js": "~0.8.26"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.8.0",
    "@angular/cli": "~6.2.1",
    "@angular/compiler-cli": "^6.1.0",
    "@angular/language-service": "^6.1.0",
    "@types/jasmine": "~2.8.8",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "~8.9.4",
    "codelyzer": "~4.3.0",
    "jasmine-core": "~2.99.1",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~3.0.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "~1.1.2",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.4.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.11.0",
    "typescript": "~2.9.2"
  }
}

Being new to front end development I’m at a loss of what may be going on under the hood which is causing the issues seen above but I plan to keep hacking away at it and hopefully figure it out – although if anyone has any suggestions that would be greatly appreciated!

Thanks,
Paul


#2

As a general piece of advice, try to not paste tons of source code into this forum. Instead, put up a running demo somewhere on GitHub and reference it for others to checkout.

The issue in your particular case may be that be that the Camunda moddle extension is not properly recognized.

Which leads me to the question: What is this import * as camundaModdleDescriptor about I see TypeScript users use so often? The JSON file exposes a single entry, which is an Object. There is no need to import * anything here (at least in JavaScript / ES6).

To verify that the import works correctly, debug your code and see if the camundaModdleDescriptor looks like this.


#3

As I’m new to Typescript I am unaware of the prevalence of import * as xyz however you were right, importing the moddle in that manner was the problem. The only change I had to make was for the import to read as follows:

import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda.json';

With that change the property groups seems to be loading properly in the panel so thanks for the help! The CSS still seems to be off however I feel much more confident in handling that issue.

Thanks once again,
Paul