<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ngDiagram</title>
    <description>The latest articles on DEV Community by ngDiagram (ngdiagram-dev).</description>
    <link>https://dev.to/ngdiagram-dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F12419%2F68de70e1-c9a6-4d47-b3ea-275efdb52fc9.jpg</url>
      <title>DEV Community: ngDiagram</title>
      <link>https://dev.to/ngdiagram-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ngdiagram-dev"/>
    <language>en</language>
    <item>
      <title>React Flow alternative for Angular: meet ngDiagram</title>
      <dc:creator>Wojciech Krzesaj</dc:creator>
      <pubDate>Wed, 05 Aug 2026 12:05:10 +0000</pubDate>
      <link>https://dev.to/ngdiagram-dev/react-flow-alternative-for-angular-meet-ngdiagram-55mc</link>
      <guid>https://dev.to/ngdiagram-dev/react-flow-alternative-for-angular-meet-ngdiagram-55mc</guid>
      <description>&lt;p&gt;React Flow has grown into the default for node-based UIs in React since its 2019 origins. Its maker, xyflow, also ships a Svelte version, Svelte Flow, but no Angular one. If you work in Angular and reach for React Flow, that gap is the wall you hit. Angular has a signals-native answer: &lt;a href="https://www.ngdiagram.dev/docs/intro/quick-start/" rel="noopener noreferrer"&gt;ngDiagram&lt;/a&gt;, a React Flow alternative built for Angular.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I work at Synergy Codes, the team behind ngDiagram. I've built with both ngDiagram and React Flow, so this comparison comes from using each.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  In short
&lt;/h2&gt;

&lt;p&gt;React Flow is React-only. Using it inside Angular means bridging 2 runtimes. ngDiagram is a native Angular diagram library built on signals, dependency injection, and OnPush: one runtime, no bridge. The library is &lt;a href="https://github.com/synergycodes/ng-diagram" rel="noopener noreferrer"&gt;open source&lt;/a&gt; under Apache 2.0, first published to npm in &lt;a href="https://www.npmjs.com/package/ng-diagram" rel="noopener noreferrer"&gt;August 2025&lt;/a&gt;, out of beta that November, with a stable 1.0 in February 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just wrap React Flow?
&lt;/h2&gt;

&lt;p&gt;Angular teams building workflow builders, org charts, pipeline editors, or data-flow canvases need a library to build the canvas on. Framework-agnostic options like GoJS and JointJS mean driving the diagram imperatively from outside Angular. GoJS is commercially licensed, while JointJS keeps its advanced tooling in the paid JointJS+ tier. React Flow is the most popular node-based UI library in React, so it keeps coming up.&lt;/p&gt;

&lt;p&gt;React Flow's limit in Angular is that it ships no Angular build. Its only way into an Angular app is a React island: an isolated React root inside your page, shipping &lt;code&gt;react&lt;/code&gt;, &lt;code&gt;react-dom&lt;/code&gt;, and React Flow alongside Angular, reached through a hand-built bridge or a third-party wrapper.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfcl1zi25p3c4mq0lwmd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfcl1zi25p3c4mq0lwmd.png" alt="Angular app with ngDiagram on the left as one Angular box; Angular app with React Flow on the right as an Angular box, a bridge, and a React box stacked vertically." width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The framework boundary, drawn out: ngDiagram sits inside Angular, while React Flow lives in a separate React box you bridge to.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Wrapping works, and the diagram itself is fine. The cost sits at the boundary, in 3 places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A second runtime.&lt;/strong&gt; Rendering React Flow means shipping &lt;code&gt;react&lt;/code&gt; and &lt;code&gt;react-dom&lt;/code&gt;, code an Angular app would never load otherwise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State stuck on the React side.&lt;/strong&gt; React Flow keeps state inside React, where Angular's change detection can't see it. Every selection, drag, and connection you need in Angular you carry across by hand (more still on Zone.js, with &lt;code&gt;runOutsideAngular()&lt;/code&gt; and &lt;code&gt;ngZone.run()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maturity that stays on the React side.&lt;/strong&gt; React Flow is proven in React apps. The bridge that holds it inside Angular is yours to write and keep working.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three are the same problem: React Flow is not Angular. Remove the boundary and the costs go with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  React Flow's mental model, native in Angular
&lt;/h2&gt;

&lt;p&gt;If you know React Flow, ngDiagram will feel familiar. The mental model carries over: nodes, edges, a map of custom node types, and connection points on each node. ngDiagram needs Angular 18+, and its only runtime dependency is &lt;code&gt;tslib&lt;/code&gt;. The foundation is pure Angular and a single runtime.&lt;/p&gt;

&lt;p&gt;The difference is what those pieces are in Angular: a custom node is an ordinary Angular component that renders through Angular directly, with nothing to sync. The model is a swappable &lt;code&gt;ModelAdapter&lt;/code&gt;, so your own store can be the single source of truth.&lt;/p&gt;

&lt;h3&gt;
  
  
  The same diagram, both ways
&lt;/h3&gt;

&lt;p&gt;Before any custom code, here is the smallest thing each library does: a diagram with 2 nodes and an edge. A direct comparison of the minimal setups shows where the runtime difference lives.&lt;/p&gt;

&lt;p&gt;React Flow uses hooks and (typically) controlled state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// React Flow&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ReactFlow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;applyNodeChanges&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;applyEdgeChanges&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Edge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OnNodesChange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OnEdgesChange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OnConnect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@xyflow/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@xyflow/react/dist/style.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialNodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Node 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// side handles, so the edge runs horizontally&lt;/span&gt;
    &lt;span class="na"&gt;sourcePosition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetPosition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Node 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;sourcePosition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetPosition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialEdges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Edge&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;e1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DiagramComponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setNodes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialNodes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEdges&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Edge&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialEdges&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onNodesChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OnNodesChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setNodes&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;nds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;applyNodeChanges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nds&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onEdgesChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OnEdgesChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setEdges&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;eds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;applyEdgeChanges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eds&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onConnect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OnConnect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setEdges&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;eds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eds&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactFlow&lt;/span&gt;
        &lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onNodesChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onNodesChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onEdgesChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onEdgesChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onConnect&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onConnect&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ngDiagram is a standalone component and a provider. State is a model you initialize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ngDiagram&lt;/span&gt;
&lt;span class="c1"&gt;// styles.scss: @import "ng-diagram/styles.css";&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;NgDiagramComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;initializeModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;provideNgDiagram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ng-diagram&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;NgDiagramComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;provideNgDiagram&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;ng-diagram [model]="model" /&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    :host {
      display: flex;
      height: 300px;
    }
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DiagramComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initializeModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Node 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Node 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;e1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// port ids shipped by the default node template&lt;/span&gt;
        &lt;span class="na"&gt;sourcePort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;port-right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;port-left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two setups look almost the same: declare your nodes and edges, then hand them to a component. The difference shows up in Angular, where the ngDiagram version runs as written and the React Flow version needs a bridge first. You wrap the React root in a Web Component and push state in through a setter per prop. Events wire back out by hand, and you own the teardown.&lt;/p&gt;

&lt;p&gt;The whole example is 2 nodes and an edge. Custom nodes are the next step.&lt;/p&gt;

&lt;h3&gt;
  
  
  A custom node, both ways
&lt;/h3&gt;

&lt;p&gt;The setup is the same in both libraries: build a component, map it to a type name, then bind the map to the canvas. Each does it the way native to its own framework.&lt;/p&gt;

&lt;p&gt;In React Flow, the node's props are typed as &lt;code&gt;NodeProps&lt;/code&gt;, and its connection points are &lt;code&gt;&amp;lt;Handle&amp;gt;&lt;/code&gt; elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// React Flow&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NodeProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@xyflow/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CustomNode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;NodeProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"custom-node"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Handle&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"target"&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Left&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Handle&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"source"&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Right&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You register the component in a &lt;code&gt;nodeTypes&lt;/code&gt; map, then pass that map to the canvas. A node renders with it when its &lt;code&gt;type&lt;/code&gt; matches the registered key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DiagramComponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodeTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;customNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CustomNode&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactFlow&lt;/span&gt; &lt;span class="na"&gt;nodeTypes&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;nodeTypes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In ngDiagram, the node arrives as a typed input, and its connection points are &lt;code&gt;&amp;lt;ng-diagram-port&amp;gt;&lt;/code&gt; elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ngDiagram&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;NgDiagramPortComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NgDiagramNodeTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ng-diagram&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;NgDiagramPortComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;div class="custom-node"&amp;gt;{{ node().data.label }}&amp;lt;/div&amp;gt;
    &amp;lt;ng-diagram-port id="port-left" type="target" side="left" /&amp;gt;
    &amp;lt;ng-diagram-port id="port-right" type="source" side="right" /&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomNodeComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;NgDiagramNodeTemplate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You register the component in an &lt;code&gt;NgDiagramNodeTemplateMap&lt;/code&gt;, then bind that map to the diagram. The node's &lt;code&gt;type&lt;/code&gt; selects its template, just as &lt;code&gt;nodeTypes&lt;/code&gt; does in React Flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;ng-diagram
    [model]="model"
    [nodeTemplateMap]="nodeTemplateMap"
  /&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DiagramComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;nodeTemplateMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NgDiagramNodeTemplateMap&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customNode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CustomNodeComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The node can reach the rest of your app through dependency injection. Inject a service and use it. The example uses an &lt;code&gt;OrderService&lt;/code&gt;, the same service your forms and tables already call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;div class="custom-node"&amp;gt;{{ status() }}&amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomNodeComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;NgDiagramNodeTemplate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;statusOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;node&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;status&lt;/code&gt; is a signal, the node updates when the order changes.&lt;/p&gt;

&lt;p&gt;When I built an app with ngDiagram, the reuse showed up right away. Generic components I already shipped elsewhere in the product (forms, cards, status widgets) dropped into nodes and just worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  How ngDiagram and React Flow handle extensibility
&lt;/h2&gt;

&lt;p&gt;Sooner or later every diagram app needs a behavior its library doesn't ship. What happens then (extend, or fork) depends on the library's extensibility model. The two libraries take different routes.&lt;/p&gt;

&lt;p&gt;React Flow extends through its React surface. The &lt;code&gt;&amp;lt;ReactFlow&amp;gt;&lt;/code&gt; component exposes a wide prop surface (interaction flags, connection rules, styling), and in controlled mode the change callbacks (&lt;code&gt;onNodesChange&lt;/code&gt;, &lt;code&gt;onEdgesChange&lt;/code&gt;) and connection events (&lt;code&gt;onConnect&lt;/code&gt;) let you intercept and reshape changes before you apply them. Beyond that, it is hooks and component composition.&lt;/p&gt;

&lt;p&gt;ngDiagram has 3 layers. Most needs are already in the global config: snapping, zoom, grouping, linking, edge routing, keyboard shortcuts, and validation callbacks, all adjustable at runtime. Events like &lt;code&gt;nodeDragEnded&lt;/code&gt; and &lt;code&gt;selectionChanged&lt;/code&gt; cover reacting to what users do. And when config and events aren't enough, every model change runs through a middleware pipeline. This is how you extend ngDiagram without forking it: I've used the pipeline to add behavior the library doesn't ship by default, like connecting edges to other edges.&lt;/p&gt;

&lt;p&gt;Locking a node to horizontal movement shows the pipeline in a few lines: intercept the moved nodes, keep the new X, and restore the Y they started at.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Horizontal movement lock in ngDiagram&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Middleware&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ng-diagram&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;horizontalLock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Middleware&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;horizontal-lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;horizontal-lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;movedIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;helpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAffectedNodeIds&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;position&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;movedIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodesToUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;movedIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodesMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialNodesMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;nodesToUpdate&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You register it alongside the defaults and pass it to the diagram:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;` &amp;lt;ng-diagram [model]="model" [middlewares]="middlewares" /&amp;gt; `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyDiagramComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;middlewares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createMiddlewares&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;horizontalLock&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The library stays untouched and your behavior sits on top of it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmjikcuku2k7hrcfnicvl.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmjikcuku2k7hrcfnicvl.gif" alt="A node dragged across the canvas, moving only left and right while its vertical position stays fixed" width="800" height="802"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A dragged node moves only horizontally. The middleware above keeps its new X and restores the Y it started at.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In React Flow the same rule goes in the controlled-mode &lt;code&gt;onNodesChange&lt;/code&gt; callback: reset each position change's Y to the node's start value, captured in &lt;code&gt;onNodeDragStart&lt;/code&gt; and held in a ref. A per-node &lt;code&gt;extent&lt;/code&gt; can pin movement too, but the callback is the general mechanism for arbitrary rules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Horizontal movement lock in React Flow&lt;/span&gt;
&lt;span class="c1"&gt;// startYOf: start positions captured in onNodeDragStart, kept in a ref&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onNodesChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NodeChange&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;position&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;startYOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;setNodes&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;nds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;applyNodeChanges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nds&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both work. The difference is where the rule lives. In React Flow it sits in the component that renders the flow. In ngDiagram it registers once and applies to every change, whatever triggered it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.ngdiagram.dev/docs/guides/middlewares" rel="noopener noreferrer"&gt;middleware guide&lt;/a&gt; covers the full pipeline: intercepting, transforming, or canceling any change.&lt;/p&gt;

&lt;h2&gt;
  
  
  ngDiagram vs React Flow, feature by feature
&lt;/h2&gt;

&lt;p&gt;ngDiagram covers the same core as React Flow, with a feature set shaped by its creators' decade of client diagramming work: the capabilities that kept proving necessary in real projects. I used those features in my own project, and they covered what I needed. For an Angular team, the deciding factor is how natively each feature fits.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;ngDiagram&lt;/th&gt;
&lt;th&gt;React Flow&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Custom nodes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Angular component (&lt;code&gt;NgDiagramNodeTemplate&lt;/code&gt;), registered in a node-type map; full Angular inside: DI and services&lt;/td&gt;
&lt;td&gt;React component (&lt;code&gt;NodeProps&lt;/code&gt;), registered in a node-type map (&lt;code&gt;nodeTypes&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Custom edges&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Angular component (&lt;code&gt;NgDiagramEdgeTemplate&lt;/code&gt;), registered in an edge-type map; SVG path via &lt;code&gt;NgDiagramBaseEdgeComponent&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;React component, registered in an edge-type map (&lt;code&gt;edgeTypes&lt;/code&gt;); SVG path via path helpers (&lt;code&gt;getBezierPath&lt;/code&gt; etc.) + &lt;code&gt;&amp;lt;BaseEdge&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ports / handles&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Named connection points via &lt;code&gt;&amp;lt;ng-diagram-port&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Named connection points via &lt;code&gt;&amp;lt;Handle&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Edge routing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in orthogonal, bezier, polyline; custom via &lt;code&gt;NgDiagramService.registerRouting()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Built-in bezier, straight, step, smoothstep, simplebezier; custom via path helpers + &lt;code&gt;&amp;lt;BaseEdge&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auto-layout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No built-in layout; documented ELK.js integration&lt;/td&gt;
&lt;td&gt;No built-in layout; documented dagre, elkjs, d3-hierarchy integrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;State &amp;amp; store&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in signal model (&lt;code&gt;initializeModel&lt;/code&gt;). Swap in a &lt;code&gt;ModelAdapter&lt;/code&gt; to make your own store the single source of truth&lt;/td&gt;
&lt;td&gt;Built-in Zustand store; controlled mode lets your store drive nodes/edges, but the internal store stays (not replaceable)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Extensibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Config, callbacks, and events for most cases&lt;br&gt;Middleware pipeline for any model change&lt;/td&gt;
&lt;td&gt;Extend through React composition, hooks, change/event callbacks, and helper components (&lt;code&gt;Panel&lt;/code&gt;, &lt;code&gt;NodeToolbar&lt;/code&gt;, &lt;code&gt;ViewportPortal&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Undo / redo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not built in yet; &lt;code&gt;Ctrl/Cmd+Z&lt;/code&gt; / &lt;code&gt;Ctrl/Cmd+Y&lt;/code&gt; are reserved and on the public roadmap (needs a custom model today)&lt;/td&gt;
&lt;td&gt;Not built in; you implement it yourself (a paid Pro example exists)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transactions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;transaction()&lt;/code&gt; batches many changes into one atomic model update: fewer renders, consistent state, and groundwork for undo/redo&lt;/td&gt;
&lt;td&gt;No transaction API; batch changes yourself in a single state update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Opt-in viewport virtualization via &lt;code&gt;virtualization.enabled&lt;/code&gt;, default off&lt;/td&gt;
&lt;td&gt;Opt-in viewport virtualization via &lt;code&gt;onlyRenderVisibleElements&lt;/code&gt;, default off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grouping&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Group nodes that contain child nodes via &lt;code&gt;groupId&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Group nodes that contain child nodes via &lt;code&gt;parentId&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Palette&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in palette components for drag-and-drop node creation, with live drag preview&lt;/td&gt;
&lt;td&gt;No built-in palette; build one following the drag-and-drop example&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resize&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in resize handles via &lt;code&gt;&amp;lt;ng-diagram-node-resize-adornment&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Built-in resize handles via &lt;code&gt;&amp;lt;NodeResizer&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rotation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in rotation handle via &lt;code&gt;&amp;lt;ng-diagram-node-rotate-adornment&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No built-in rotation; build one following the rotatable-node example&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Minimap&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minimap &lt;code&gt;&amp;lt;ng-diagram-minimap&amp;gt;&lt;/code&gt;: node overview, viewport indicator, pan/zoom navigation&lt;/td&gt;
&lt;td&gt;Minimap &lt;code&gt;&amp;lt;MiniMap&amp;gt;&lt;/code&gt;: node overview, viewport indicator, pan/zoom navigation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Theming&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CSS variables; light and dark out of the box; Tailwind supported&lt;/td&gt;
&lt;td&gt;CSS variables; light and dark via &lt;code&gt;colorMode&lt;/code&gt; (default light); Tailwind supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Touch / mobile&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Supported: pinch-zoom, 2-finger pan, long-press select, tap/drag/resize/rotate, drag-connect, tap-to-connect via linking mode (&lt;code&gt;startLinking()&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Supported: pinch-zoom, pan, tap-to-connect, drag-connect, auto-pan, connection radius&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Accessibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not built in yet; full support is on the public roadmap. Keyboard shortcuts cover editing (copy/paste, move, zoom), not navigation. Nodes are real DOM, so you can add ARIA to node content today&lt;/td&gt;
&lt;td&gt;Documented built-in a11y: focusable nodes/edges, keyboard move, ARIA labels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI-assisted dev&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Official &lt;code&gt;@ng-diagram/mcp&lt;/code&gt; server puts the docs and API into your AI assistant&lt;/td&gt;
&lt;td&gt;No official MCP server (a deliberate docs-first choice); publishes &lt;code&gt;llms.txt&lt;/code&gt; docs endpoints for AI assistants instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bundle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One library; only runtime dependency is &lt;code&gt;tslib&lt;/code&gt;; no extra framework in the bundle&lt;/td&gt;
&lt;td&gt;The library plus the React runtime (&lt;code&gt;react&lt;/code&gt; + &lt;code&gt;react-dom&lt;/code&gt;) an Angular app would not otherwise ship&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Current bundle sizes: bundlephobia for &lt;a href="https://bundlephobia.com/package/ng-diagram" rel="noopener noreferrer"&gt;ng-diagram&lt;/a&gt; and &lt;a href="https://bundlephobia.com/package/@xyflow/react" rel="noopener noreferrer"&gt;@xyflow/react&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How mature is ngDiagram?
&lt;/h2&gt;

&lt;p&gt;ngDiagram hit a stable v1.0 in February 2026 and is at v1.2 as of mid-2026. It is open source under Apache 2.0, tracks the latest 3 Angular versions, and is built by &lt;a href="https://synergycodes.com/" rel="noopener noreferrer"&gt;Synergy Codes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Development has not slowed since: each release brings DX improvements and features the community asked for.&lt;/p&gt;

&lt;p&gt;Beyond examples, the team ships maintained starter-kit &lt;a href="https://www.ngdiagram.dev/docs/templates" rel="noopener noreferrer"&gt;templates&lt;/a&gt; that you clone and customize for your own use case, with more to come. They come from the same place as the feature set: what client work kept proving necessary.&lt;/p&gt;

&lt;p&gt;Developers are already building the first projects using it, collected in the growing &lt;a href="https://www.ngdiagram.dev/docs/showcases/" rel="noopener noreferrer"&gt;showcases&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The library has been well received: it has passed &lt;a href="https://github.com/synergycodes/ng-diagram" rel="noopener noreferrer"&gt;500 GitHub stars&lt;/a&gt;, the community has started writing its own tutorials on Medium, and the &lt;a href="https://x.com/angular/status/2022355086970626475" rel="noopener noreferrer"&gt;official Angular account shared ngDiagram&lt;/a&gt; in February 2026.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foikb9lhxpi372fvcgy8i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foikb9lhxpi372fvcgy8i.png" alt="The official Angular account sharing ngDiagram, February 2026" width="591" height="637"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The official Angular account sharing ngDiagram, February 2026 (&lt;a href="https://x.com/angular/status/2022355086970626475" rel="noopener noreferrer"&gt;source&lt;/a&gt;).&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When to choose which, and how to start
&lt;/h2&gt;

&lt;p&gt;The rule tracks your framework, with one exception:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your stack&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Your app is React&lt;/td&gt;
&lt;td&gt;React Flow&lt;/td&gt;
&lt;td&gt;Mature, MIT-licensed, proven in production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A new Angular build&lt;/td&gt;
&lt;td&gt;ngDiagram&lt;/td&gt;
&lt;td&gt;No React layer to ship or bridge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Angular already running a React Flow island&lt;/td&gt;
&lt;td&gt;Keep it. Treat as tech debt, plan a migration&lt;/td&gt;
&lt;td&gt;The island works. The second runtime, bridge, and state sync stay as ongoing cost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a fresh Angular build, I don't see a reason to start with React Flow. The framework boundary alone is enough to prefer a native option. Wrapping is usually chosen for React Flow's maturity. The question is whether the boundary cost is worth it in your app.&lt;/p&gt;

&lt;p&gt;The exception is if you already run React Flow inside Angular. A rewrite tomorrow is rarely worth the risk. Keep it, and revisit when the bridge starts to bite: a major framework upgrade, nodes that need deeper access to your Angular app, or the next big diagram feature.&lt;/p&gt;

&lt;p&gt;Sticking with what you know is reasonable, but in Angular, React Flow adds one more thing to maintain: the bridge between the 2 frameworks. A native library drops it. Try ngDiagram and see how it feels. Adding it takes one install, one provider, and a styles import. Start with the &lt;a href="https://www.ngdiagram.dev/docs/examples" rel="noopener noreferrer"&gt;interactive examples&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Building a custom diagram editor, or taking an existing one further, is the kind of &lt;a href="https://www.synergycodes.com/contact" rel="noopener noreferrer"&gt;work Synergy Codes does&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to build a BPMN editor in Angular with ngDiagram</title>
      <dc:creator>Jacek Dębek</dc:creator>
      <pubDate>Fri, 10 Jul 2026 13:18:11 +0000</pubDate>
      <link>https://dev.to/ngdiagram-dev/how-to-build-a-bpmn-editor-in-angular-with-ngdiagram-4id0</link>
      <guid>https://dev.to/ngdiagram-dev/how-to-build-a-bpmn-editor-in-angular-with-ngdiagram-4id0</guid>
      <description>&lt;h2&gt;
  
  
  In short:
&lt;/h2&gt;

&lt;p&gt;This tutorial builds a BPMN editor on &lt;a href="https://github.com/synergycodes/ng-diagram" rel="noopener noreferrer"&gt;ngDiagram&lt;/a&gt;. The editor gets a palette of shapes you drag onto the canvas, swimlanes, 3 typed connection styles, and per-lane auto-layout – about 1,700 lines of application code in total. The complete project is &lt;a href="https://github.com/synergycodes/bpmn-editor" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ck6dbh6a52uwzknz2rq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ck6dbh6a52uwzknz2rq.gif" alt="ngDiagram BPMN editor — adding from palette and layout" width="760" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What we will build
&lt;/h2&gt;

&lt;p&gt;The editor breaks down into 9 features, and each one maps to one ngDiagram API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A working canvas&lt;/strong&gt; – &lt;code&gt;provideNgDiagram()&lt;/code&gt;, &lt;code&gt;initializeModel()&lt;/code&gt;, a sized &lt;code&gt;&amp;lt;ng-diagram&amp;gt;&lt;/code&gt; host&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A typed model&lt;/strong&gt; – your BPMN subset as type strings and &lt;code&gt;Node&amp;lt;T&amp;gt;&lt;/code&gt; / &lt;code&gt;Edge&amp;lt;T&amp;gt;&lt;/code&gt; data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node shapes&lt;/strong&gt; – one component per shape family (events, tasks, gateways) plus one for the lane, registered in a template map&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edges and arrowheads&lt;/strong&gt; – &lt;code&gt;ng-diagram-base-edge&lt;/code&gt; plus SVG markers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A drag-and-drop palette&lt;/strong&gt; – plain data objects inside &lt;code&gt;&amp;lt;ng-diagram-palette-item&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swimlanes&lt;/strong&gt; – group nodes (&lt;code&gt;isGroup&lt;/code&gt;), membership rules, &lt;code&gt;NgDiagramGroupsService&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Editor rules&lt;/strong&gt; – one typed config: connection checks, edge creation, resize limits, routing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-layout&lt;/strong&gt; – bring your own engine (elkjs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Theming&lt;/strong&gt; – &lt;code&gt;--ngd-*&lt;/code&gt; CSS variables&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step below adds one feature and introduces the API behind it. The code samples come from the finished project – clone the repo and follow along. The article walks through the code that matters for ngDiagram; the repo holds the rest (styles, the toolbar and palette UI, helper internals). BPMN is only the example: the same building blocks make org charts, data pipelines, or network diagrams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not bpmn-js?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://bpmn.io/toolkit/bpmn-js/" rel="noopener noreferrer"&gt;bpmn-js&lt;/a&gt; is the right choice when you need standard BPMN 2.0 XML import and export – it is the fastest way to get that. It comes with 2 costs. Its license requires a visible watermark on every diagram, with no public paid option to remove it. And it has no official Angular wrapper, so you maintain the bridge between its event bus and Angular yourself.&lt;/p&gt;

&lt;p&gt;This tutorial uses a different approach: a general diagramming canvas plus your own small BPMN model. Real diagrams use a small part of the notation, so you will define your own subset either way. ngDiagram is Angular-native – nodes are components, state is signals, theming is CSS variables – so every step below stays inside normal Angular patterns.&lt;/p&gt;

&lt;p&gt;Full disclosure: I build diagramming tools at &lt;a href="https://www.synergycodes.com" rel="noopener noreferrer"&gt;Synergy Codes&lt;/a&gt;, the company behind ngDiagram. Judge the code below for yourself. GoJS and JointJS are alternative diagramming libraries you could build a BPMN editor on – a comparison of all 4 options is at the end of the article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 – set up the canvas
&lt;/h2&gt;

&lt;p&gt;Start by installing the &lt;a href="https://www.npmjs.com/package/ng-diagram" rel="noopener noreferrer"&gt;library&lt;/a&gt;: &lt;code&gt;npm i ng-diagram&lt;/code&gt;. The library supports Angular 18 or newer; the project in this tutorial uses Angular 19. A canvas then needs 3 things: the provider, the engine stylesheet, and a host element with a real size. If the diagram renders blank, one of these 3 is missing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* src/styles.scss */&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;'ng-diagram/styles.css'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;ng-diagram&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;provideNgDiagram()&lt;/code&gt; goes on the component that wraps the whole editor – toolbar, palette, and canvas. Do not put it at the application root: the services attach to the diagram's DOM subtree, so they must live inside it. This scoping also keeps 2 diagrams on one page independent, if you ever need it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/pages/editor-page.component.ts (imports omitted)&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-editor-page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ToolbarComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PaletteComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DiagramComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;changeDetection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChangeDetectionStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OnPush&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ElkLayoutService and SwimlaneService are app services for steps 6 and 8.&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;provideNgDiagram&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;ElkLayoutService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SwimlaneService&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./editor-page.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// toolbar + palette + diagram&lt;/span&gt;
  &lt;span class="na"&gt;styleUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./editor-page.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EditorPageComponent&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the wrapper lives the diagram component – the owner of the &lt;code&gt;&amp;lt;ng-diagram&amp;gt;&lt;/code&gt; canvas. In step 1 the class only creates the model; &lt;code&gt;initializeModel&lt;/code&gt; needs an injection context, and a field initializer is one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/diagram/diagram.component.ts (imports omitted)&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-bpmn-diagram&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;NgDiagramComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NgDiagramBackgroundComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NgDiagramMarkerComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;changeDetection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChangeDetectionStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OnPush&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./diagram.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./diagram.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DiagramComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;model$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initializeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;seedModel&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// seed nodes and edges&lt;/span&gt;

  &lt;span class="c1"&gt;// Added by the later steps: the template maps (steps 3–4), the config&lt;/span&gt;
  &lt;span class="c1"&gt;// (step 7), and the drop/resize handlers (step 6).&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its template binds everything to &lt;code&gt;&amp;lt;ng-diagram&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/app/bpmn/diagram/diagram.component.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ng-diagram&lt;/span&gt;
  &lt;span class="na"&gt;[model]=&lt;/span&gt;&lt;span class="s"&gt;"model$"&lt;/span&gt;
  &lt;span class="na"&gt;[config]=&lt;/span&gt;&lt;span class="s"&gt;"config"&lt;/span&gt;
  &lt;span class="na"&gt;[nodeTemplateMap]=&lt;/span&gt;&lt;span class="s"&gt;"nodeTemplateMap"&lt;/span&gt;
  &lt;span class="na"&gt;[edgeTemplateMap]=&lt;/span&gt;&lt;span class="s"&gt;"edgeTemplateMap"&lt;/span&gt;
  &lt;span class="na"&gt;(paletteItemDropped)=&lt;/span&gt;&lt;span class="s"&gt;"onPaletteItemDropped($event)"&lt;/span&gt;
  &lt;span class="na"&gt;(nodeResized)=&lt;/span&gt;&lt;span class="s"&gt;"onNodeResized($event)"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ng-diagram-background&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"dots"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ng-diagram&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This template is the finished shape – the config, the template maps, and the event handlers come from steps 3–7. The minimal working canvas is just &lt;code&gt;&amp;lt;ng-diagram [model]="model$" /&amp;gt;&lt;/code&gt; with the dotted background child.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the app:&lt;/strong&gt; after &lt;code&gt;npm start&lt;/code&gt;, the seeded diagram renders – pan and zoom already work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 – describe BPMN as data
&lt;/h2&gt;

&lt;p&gt;In ngDiagram, a diagram is data: an element on the canvas is a plain object with a &lt;code&gt;type&lt;/code&gt; string and a &lt;code&gt;data&lt;/code&gt; object. The whole "which BPMN" decision lives in a single model file, and it defines exactly those 2 things for every element: its type strings and its data shapes.&lt;/p&gt;

&lt;p&gt;BPMN 2.0 defines well over 100 graphical elements, but you do not need them all. According to &lt;a href="https://eprints.qut.edu.au/12916/1/12916.pdf" rel="noopener noreferrer"&gt;zur Muehlen and Recker's study&lt;/a&gt; of 120 real-world BPMN diagrams, the average model uses about 9 distinct constructs. So this editor models 9 shapes: events, tasks, gateways, and the swimlane ("lane" from here on).&lt;/p&gt;

&lt;p&gt;First, the type strings. A &lt;code&gt;type&lt;/code&gt; selects which Angular component renders the element – in step 3 a template map does the matching. Every object in the model carries one: a seed node looks like &lt;code&gt;{ id: 'start', type: BpmnNodeType.StartEvent, position: …, data: { label: 'Start' } }&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/model/bpmn.model.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bpmn-task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ExclusiveGateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bpmn-exclusive-gateway&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Swimlane&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bpmn-swimlane&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ...9 type strings total – events, tasks, gateways, lane&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BPMN_EDGE_TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bpmn-edge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, the data shapes. Each element family declares an interface for its &lt;code&gt;data&lt;/code&gt; object – the fields your app stores on the element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/model/bpmn.model.ts (continued)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;BpmnNodeData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SwimlaneData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** Vertical stacking order (0 = topmost lane). */&lt;/span&gt;
  &lt;span class="nl"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;BpmnEdgeData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/** Line style. Defined as the BpmnEdgeKind const in the repo. */&lt;/span&gt;
  &lt;span class="nl"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sequence&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;association&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** Shown at the middle of the edge; editable in step 4. */&lt;/span&gt;
  &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// (layout flags and index signatures trimmed – see the repo)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BpmnNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BpmnNodeData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SwimlaneNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SwimlaneData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BpmnEdge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Edge&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BpmnEdgeData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The aliases at the end hand these interfaces to ngDiagram's generics. From here on, every API that touches a node – templates, updates, event payloads – sees &lt;code&gt;Node&amp;lt;BpmnNodeData&amp;gt;&lt;/code&gt;, so a typo in a field name fails at compile time instead of rendering a broken node.&lt;/p&gt;

&lt;p&gt;The edge &lt;code&gt;kind&lt;/code&gt; comes back in step 4, where it picks the line style, and the lane &lt;code&gt;order&lt;/code&gt; drives the vertical stacking in step 6.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 – nodes are Angular components
&lt;/h2&gt;

&lt;p&gt;Every BPMN shape is a normal Angular component, and each of the 3 shape families – events, tasks, gateways – gets its own. This step builds the task template and registers it at the end.&lt;/p&gt;

&lt;h3&gt;
  
  
  The template structure
&lt;/h3&gt;

&lt;p&gt;ngDiagram asks a node template for exactly 2 things: implement &lt;code&gt;NgDiagramNodeTemplate&amp;lt;T&amp;gt;&lt;/code&gt; – which boils down to one required input named &lt;code&gt;node&lt;/code&gt; – and render whatever markup you like. Inside that markup, 2 standard library pieces appear in every template: the &lt;code&gt;ngDiagramNodeSelected&lt;/code&gt; directive on the wrapper (it adds the &lt;code&gt;ng-diagram-node-selected&lt;/code&gt; class while the node is selected, so your CSS styles that state) and &lt;code&gt;&amp;lt;ng-diagram-port&amp;gt;&lt;/code&gt; elements for connections. The task template's structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/app/bpmn/diagram/templates/nodes/task-node/task-node.component.html (structure) --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"task-node"&lt;/span&gt; &lt;span class="na"&gt;ngDiagramNodeSelected&lt;/span&gt; &lt;span class="na"&gt;[node]=&lt;/span&gt;&lt;span class="s"&gt;"node()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- your own markup: the rounded rectangle, the type icon, the label --&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ng-diagram-port&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"top"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"both"&lt;/span&gt; &lt;span class="na"&gt;side=&lt;/span&gt;&lt;span class="s"&gt;"top"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ng-diagram-port&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"right"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"both"&lt;/span&gt; &lt;span class="na"&gt;side=&lt;/span&gt;&lt;span class="s"&gt;"right"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ng-diagram-port&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"bottom"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"both"&lt;/span&gt; &lt;span class="na"&gt;side=&lt;/span&gt;&lt;span class="s"&gt;"bottom"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ng-diagram-port&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"left"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"both"&lt;/span&gt; &lt;span class="na"&gt;side=&lt;/span&gt;&lt;span class="s"&gt;"left"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A port's &lt;code&gt;type&lt;/code&gt; takes &lt;code&gt;source&lt;/code&gt;, &lt;code&gt;target&lt;/code&gt;, or &lt;code&gt;both&lt;/code&gt; and controls whether a connection may start or end there; edges reference ports by their &lt;code&gt;id&lt;/code&gt;. Hovering anywhere over the node lights the ports up – that is the engine's &lt;code&gt;ng-diagram-port-hoverable-over-node&lt;/code&gt; host class, set once in the base class below.&lt;/p&gt;

&lt;h3&gt;
  
  
  The editable label
&lt;/h3&gt;

&lt;p&gt;Now the custom part – the markup in the middle. The label is editable inline: a double-click swaps the text for a native &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;, and 2 ngDiagram attributes make an input inside a node usable – &lt;code&gt;data-no-drag&lt;/code&gt; and &lt;code&gt;data-no-pan&lt;/code&gt; keep typing from dragging the node or panning the canvas. The editing state (&lt;code&gt;editing()&lt;/code&gt;, &lt;code&gt;commit&lt;/code&gt;, &lt;code&gt;cancel&lt;/code&gt;) comes from a shared app directive, explained below the template. (&lt;code&gt;.inline-editor&lt;/code&gt; carries the field's styling and &lt;code&gt;&amp;lt;app-icon&amp;gt;&lt;/code&gt; renders from the app's SVG sprite – see &lt;a href="https://github.com/synergycodes/bpmn-editor" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- task-node.component.html – the markup inside the wrapper --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"task"&lt;/span&gt; &lt;span class="na"&gt;[class.task--typed]=&lt;/span&gt;&lt;span class="s"&gt;"type() !== T.Task"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  @switch (type()) {
    @case (T.UserTask) { &lt;span class="nt"&gt;&amp;lt;app-icon&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"task__icon"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"person"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; }
    @case (T.ServiceTask) { &lt;span class="nt"&gt;&amp;lt;app-icon&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"task__icon"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"gear"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; }
  }

  @if (editing()) {
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
      &lt;span class="na"&gt;#editor&lt;/span&gt;
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"inline-editor task__input"&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
      &lt;span class="na"&gt;data-no-drag=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
      &lt;span class="na"&gt;data-no-pan=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
      &lt;span class="na"&gt;[value]=&lt;/span&gt;&lt;span class="s"&gt;"label()"&lt;/span&gt;
      &lt;span class="na"&gt;(dblclick)=&lt;/span&gt;&lt;span class="s"&gt;"$event.stopPropagation()"&lt;/span&gt;
      &lt;span class="na"&gt;(keydown.enter)=&lt;/span&gt;&lt;span class="s"&gt;"commit(editor.value)"&lt;/span&gt;
      &lt;span class="na"&gt;(keydown.escape)=&lt;/span&gt;&lt;span class="s"&gt;"cancel()"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  } @else {
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"task__label"&lt;/span&gt; &lt;span class="na"&gt;(dblclick)=&lt;/span&gt;&lt;span class="s"&gt;"startEdit($event)"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Double-click to rename"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      {{ label() }}
    &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  }
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The component and its base
&lt;/h3&gt;

&lt;p&gt;The component class is only metadata – the logic sits in a shared base:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/diagram/templates/nodes/task-node/task-node.component.ts (imports omitted)&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-task-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;IconComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NgDiagramPortComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NgDiagramNodeSelectedDirective&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;changeDetection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChangeDetectionStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OnPush&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./task-node.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./task-node.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskNodeComponent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BpmnElementNode&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BpmnElementNode&lt;/code&gt; is shared by all 3 element families. It implements the required &lt;code&gt;node&lt;/code&gt; input, exposes the type and label computeds the template reads, and saves edited labels through &lt;code&gt;updateNodeData&lt;/code&gt; on &lt;code&gt;NgDiagramModelService&lt;/code&gt; – the injectable service that owns all model reads and writes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/diagram/templates/nodes/bpmn-element-node.ts (imports omitted)&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Directive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Engine-provided hover styling: highlights all ports while over the node.&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ng-diagram-port-hoverable-over-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BpmnElementNode&lt;/span&gt;
  &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;InlineEditableLabel&lt;/span&gt;
  &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;NgDiagramNodeTemplate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BpmnNodeData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NgDiagramModelService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BpmnNodeData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="cm"&gt;/** Templates only see class members – expose the type consts for @case. */&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;node&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;node&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;saveLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateNodeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;node&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;node&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The editing state machine itself – enter on double-click, focus the field, save on Enter or a press outside, cancel on Escape – lives one level higher in &lt;code&gt;InlineEditableLabel&lt;/code&gt;, a small app directive shared by all node and edge templates; a host plugs in by implementing &lt;code&gt;saveLabel&lt;/code&gt; (see &lt;a href="https://github.com/synergycodes/bpmn-editor" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EventNodeComponent&lt;/code&gt; and &lt;code&gt;GatewayNodeComponent&lt;/code&gt; are the same kind of shell: their templates &lt;code&gt;@switch&lt;/code&gt; on the node type to pick the glyph – a circle style per event type, a diamond with an x or + mark – with the editable label below it. Both are in the repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Registering the templates
&lt;/h3&gt;

&lt;p&gt;With the components ready, register them. A template map matches each &lt;code&gt;type&lt;/code&gt; string from step 2 to a component, and the shape families are arrays in the model file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/model/bpmn.model.ts – the shape families: events, tasks, gateways.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EVENT_TYPES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StartEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EndEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IntermediateEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TASK_TYPES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UserTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ServiceTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GATEWAY_TYPES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ExclusiveGateway&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ParallelGateway&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/diagram/diagram.component.ts&lt;/span&gt;
&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;nodeTemplateMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NgDiagramNodeTemplateMap&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;EVENT_TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;EventNodeComponent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;TASK_TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TaskNodeComponent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;GATEWAY_TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GatewayNodeComponent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Swimlane&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SwimlaneNodeComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// the lane – a group template, step 6&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In the app:&lt;/strong&gt; double-click a task's label and rename it – Enter saves, Escape cancels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 – edges: 3 line styles, 1 component
&lt;/h2&gt;

&lt;p&gt;BPMN needs 3 connection styles – solid sequence flows, dashed message flows, dotted associations – and one edge component covers all of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The template structure
&lt;/h3&gt;

&lt;p&gt;An edge template mirrors the node contract: implement &lt;code&gt;NgDiagramEdgeTemplate&amp;lt;T&amp;gt;&lt;/code&gt; – one required input named &lt;code&gt;edge&lt;/code&gt; – and render &lt;code&gt;&amp;lt;ng-diagram-base-edge&amp;gt;&lt;/code&gt;, the library component that draws the routed path. Your template only configures it: the dasharray and the arrowhead ids come from computeds that read &lt;code&gt;edge.data.kind&lt;/code&gt; (shown below).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/app/bpmn/diagram/templates/edges/bpmn-edge.component.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ng-diagram-base-edge&lt;/span&gt;
  &lt;span class="na"&gt;[edge]=&lt;/span&gt;&lt;span class="s"&gt;"edge()"&lt;/span&gt;
  &lt;span class="na"&gt;[strokeDasharray]=&lt;/span&gt;&lt;span class="s"&gt;"dasharray()"&lt;/span&gt;
  &lt;span class="na"&gt;[sourceArrowhead]=&lt;/span&gt;&lt;span class="s"&gt;"sourceArrow()"&lt;/span&gt;
  &lt;span class="na"&gt;[targetArrowhead]=&lt;/span&gt;&lt;span class="s"&gt;"targetArrow()"&lt;/span&gt;
  &lt;span class="na"&gt;[attr.title]=&lt;/span&gt;&lt;span class="s"&gt;"!label() &amp;amp;&amp;amp; !editing() ? 'Double-click to add a label' : null"&lt;/span&gt;
  &lt;span class="na"&gt;(dblclick)=&lt;/span&gt;&lt;span class="s"&gt;"startEdit($event)"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  @if (editing() || label()) {
    &lt;span class="nt"&gt;&amp;lt;ng-diagram-base-edge-label&lt;/span&gt; &lt;span class="na"&gt;[id]=&lt;/span&gt;&lt;span class="s"&gt;"edge().id + '-label'"&lt;/span&gt; &lt;span class="na"&gt;[positionOnEdge]=&lt;/span&gt;&lt;span class="s"&gt;"0.5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- label text, or an input while editing – see the repo --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ng-diagram-base-edge-label&amp;gt;&lt;/span&gt;
  }
&lt;span class="nt"&gt;&amp;lt;/ng-diagram-base-edge&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The inline label
&lt;/h3&gt;

&lt;p&gt;Labels sit in &lt;code&gt;&amp;lt;ng-diagram-base-edge-label&amp;gt;&lt;/code&gt;, positioned along the path – &lt;code&gt;0.5&lt;/code&gt; is the middle, which fits the "yes" / "no" labels on gateway exits. Double-click on the edge edits the label inline, the same pattern as node labels; an empty value removes it, and the &lt;code&gt;title&lt;/code&gt; tooltip invites the user to add one.&lt;/p&gt;

&lt;p&gt;The class is again mostly metadata plus a few computeds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/diagram/templates/edges/bpmn-edge.component.ts (imports omitted)&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-bpmn-edge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;NgDiagramBaseEdgeComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NgDiagramBaseEdgeLabelComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;changeDetection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChangeDetectionStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OnPush&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./bpmn-edge.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./bpmn-edge.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BpmnEdgeComponent&lt;/span&gt;
  &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;InlineEditableLabel&lt;/span&gt;
  &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;NgDiagramEdgeTemplate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BpmnEdgeData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;edge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Edge&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BpmnEdgeData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;computed&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BpmnEdgeKind&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;BpmnEdgeKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;BpmnEdgeKind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;dasharray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;BpmnEdgeKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;6 4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;BpmnEdgeKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Association&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1 4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// sourceArrow / targetArrow pick the marker ids the same way,&lt;/span&gt;
  &lt;span class="c1"&gt;// and saveLabel stores the edited label – see the repo.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Selection styling
&lt;/h3&gt;

&lt;p&gt;Selection needs no code at all. The base edge adds the &lt;code&gt;.selected&lt;/code&gt; class to itself and reads the &lt;code&gt;--edge-*&lt;/code&gt; CSS variables, so the selected stroke is 2 CSS rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* src/app/bpmn/diagram/templates/edges/bpmn-edge.component.scss */&lt;/span&gt;
&lt;span class="nt"&gt;ng-diagram-base-edge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;--edge-stroke-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="mi"&gt;.6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;ng-diagram-base-edge&lt;/span&gt;&lt;span class="nc"&gt;.selected&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;--edge-stroke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;c-brand&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="na"&gt;--edge-stroke-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="mi"&gt;.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/* (label chip styles – see the repo) */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Arrowheads
&lt;/h3&gt;

&lt;p&gt;Arrowheads are plain SVG markers, defined once inside &lt;code&gt;&amp;lt;ng-diagram-marker&amp;gt;&lt;/code&gt;, next to the &lt;code&gt;&amp;lt;ng-diagram&amp;gt;&lt;/code&gt; element in the diagram component's template. One useful detail: &lt;code&gt;fill="context-stroke"&lt;/code&gt; makes each marker inherit the color of its edge, so themed (and selected) edges get matching arrowheads for free.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/app/bpmn/diagram/diagram.component.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ng-diagram-marker&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&amp;gt;&amp;lt;defs&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;marker&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"bpmn-seq-arrow"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 10 10"&lt;/span&gt; &lt;span class="na"&gt;refX=&lt;/span&gt;&lt;span class="s"&gt;"9"&lt;/span&gt; &lt;span class="na"&gt;refY=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;
      &lt;span class="na"&gt;markerWidth=&lt;/span&gt;&lt;span class="s"&gt;"9"&lt;/span&gt; &lt;span class="na"&gt;markerHeight=&lt;/span&gt;&lt;span class="s"&gt;"9"&lt;/span&gt; &lt;span class="na"&gt;orient=&lt;/span&gt;&lt;span class="s"&gt;"auto-start-reverse"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M0,0 L10,5 L0,10 z"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"context-stroke"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/marker&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- message-flow open arrow + open circle: see the repo --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/defs&amp;gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ng-diagram-marker&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To put a marker on an edge, pass its id to the &lt;code&gt;[sourceArrowhead]&lt;/code&gt; / &lt;code&gt;[targetArrowhead]&lt;/code&gt; inputs from the template structure above. That is all the arrow computeds do – &lt;code&gt;undefined&lt;/code&gt; means no arrowhead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/diagram/templates/edges/bpmn-edge.component.ts (fragment)&lt;/span&gt;
&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;targetArrow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;BpmnEdgeKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bpmn-msg-end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;BpmnEdgeKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Association&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bpmn-seq-arrow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Registration and routing
&lt;/h3&gt;

&lt;p&gt;The edge component registers in an &lt;code&gt;NgDiagramEdgeTemplateMap&lt;/code&gt;, exactly like the node map in step 3 (see &lt;a href="https://github.com/synergycodes/bpmn-editor" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;). Orthogonal routing – the standard style for BPMN diagrams – is one line in step 7's config: &lt;code&gt;edgeRouting: { defaultRouting: 'orthogonal' }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the app:&lt;/strong&gt; double-click an edge to give it a label. Select the edge – the stroke and the arrowhead turn brand-colored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 – a palette from plain data
&lt;/h2&gt;

&lt;p&gt;A palette is a side panel with tiles that users drag onto the canvas to create new elements. ngDiagram's part is one wrapper component: put your own tile markup inside &lt;code&gt;&amp;lt;ng-diagram-palette-item [item]="…"&amp;gt;&lt;/code&gt; and the engine makes the tile draggable. &lt;code&gt;&amp;lt;ng-diagram-palette-item-preview&amp;gt;&lt;/code&gt; is the optional ghost shown while dragging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/app/bpmn/palette/palette.component.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ng-diagram-palette-item&lt;/span&gt; &lt;span class="na"&gt;[item]=&lt;/span&gt;&lt;span class="s"&gt;"entry.item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tile"&lt;/span&gt; &lt;span class="na"&gt;[title]=&lt;/span&gt;&lt;span class="s"&gt;"entry.label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tile__art"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;app-icon&lt;/span&gt; &lt;span class="na"&gt;[name]=&lt;/span&gt;&lt;span class="s"&gt;"entry.glyph"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tile__label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ entry.label }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ng-diagram-palette-item-preview&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tile-preview"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;app-icon&lt;/span&gt; &lt;span class="na"&gt;[name]=&lt;/span&gt;&lt;span class="s"&gt;"entry.glyph"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ng-diagram-palette-item-preview&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ng-diagram-palette-item&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;item&lt;/code&gt; input is a plain object describing the node the drop will create. This editor keeps all entries in a const array – adding a shape to the palette is one entry, no component work. The lane's &lt;code&gt;item&lt;/code&gt; also prepares step 6, because a lane is a node with &lt;code&gt;isGroup: true&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/model/palette-data.ts – the `item` field of the lane's entry.&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BpmnNodeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Swimlane&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;resizable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;rotatable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;autoSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;LANE_DEFAULT_SIZE&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// `order` is assigned on drop (append to the bottom of the stack).&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New Lane&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;SwimlaneData&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;BasePaletteItemData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;autoSize: false&lt;/code&gt; with an explicit &lt;code&gt;size&lt;/code&gt; appears on every element in this editor: BPMN shapes have fixed proportions, and fixed sizes are also what the swimlane math in step 6 relies on.&lt;/p&gt;

&lt;p&gt;On drop, ngDiagram creates the real node and hands it to you in the &lt;code&gt;(paletteItemDropped)&lt;/code&gt; event, together with &lt;code&gt;dropPosition&lt;/code&gt; in diagram coordinates. Step 6 uses both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the app:&lt;/strong&gt; drag a tile from the palette onto the canvas – a new node appears at the drop point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 – swimlanes are groups
&lt;/h2&gt;

&lt;p&gt;ngDiagram models containers as groups. A lane is a node with &lt;code&gt;isGroup: true&lt;/code&gt;, members point to it through &lt;code&gt;groupId&lt;/code&gt;, and the engine handles the rest: membership on drag, highlight when a node hovers over a lane, children moving with their lane.&lt;/p&gt;

&lt;h3&gt;
  
  
  Membership rules
&lt;/h3&gt;

&lt;p&gt;One function in the config decides which nodes may join a lane:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/diagram/diagram-config.ts&lt;/span&gt;
&lt;span class="nx"&gt;grouping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Only flow elements may be grouped, and only into a swimlane.&lt;/span&gt;
  &lt;span class="nl"&gt;canGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;isSwimlane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isSwimlane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The lane template
&lt;/h3&gt;

&lt;p&gt;A group template follows the same contract as the node templates from step 3, with &lt;code&gt;NgDiagramGroupNodeTemplate&lt;/code&gt; as the interface. The lane wraps itself in the engine's resize adornment – the selection frame with drag handles – and adds one more directive, &lt;code&gt;ngDiagramGroupHighlighted&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/app/bpmn/diagram/templates/nodes/swimlane-node/swimlane-node.component.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ng-diagram-node-resize-adornment&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"lane"&lt;/span&gt; &lt;span class="na"&gt;ngDiagramNodeSelected&lt;/span&gt; &lt;span class="na"&gt;ngDiagramGroupHighlighted&lt;/span&gt; &lt;span class="na"&gt;[node]=&lt;/span&gt;&lt;span class="s"&gt;"node()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"lane__header"&lt;/span&gt; &lt;span class="na"&gt;[style.width.px]=&lt;/span&gt;&lt;span class="s"&gt;"headerWidth"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"lane__controls"&lt;/span&gt; &lt;span class="na"&gt;data-no-drag=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;data-no-pan=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"lane__btn"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Move lane up"&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"moveUp($event)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;app-icon&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"triangle-up"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"lane__btn"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Move lane down"&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"moveDown($event)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;app-icon&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"triangle-down"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- inline-editable title – see the repo --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"lane__body"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ng-diagram-node-resize-adornment&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ngDiagramGroupHighlighted&lt;/code&gt; exposes the drop-zone state: while a node hovers over the lane, it adds &lt;code&gt;.ng-diagram-group-highlight&lt;/code&gt;, and you style that class with your own CSS. The reorder buttons in the header sit behind &lt;code&gt;data-no-drag&lt;/code&gt;, so clicking them never drags the lane.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parenting on drop
&lt;/h3&gt;

&lt;p&gt;For elements already on the diagram, this needs no code at all: dropping a node onto a group adds it to the group automatically. Only a drop from the palette needs extra handling – the engine creates the node, but into which lane it lands is your call. The handler checks which lane rectangle contains the drop point; &lt;code&gt;paletteItemDropped&lt;/code&gt; fires after the node is committed to the model, so it can re-parent right away through &lt;code&gt;NgDiagramGroupsService&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/diagram/diagram.component.ts&lt;/span&gt;
&lt;span class="nf"&gt;onPaletteItemDropped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PaletteItemDroppedEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isSwimlane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;swimlanes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onLaneAdded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// stack + width-match the new lane&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lane&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;swimlanes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;laneAtPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dropPosition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lane&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addToGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lane&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lane geometry
&lt;/h3&gt;

&lt;p&gt;The lane geometry lives in a dedicated &lt;code&gt;SwimlaneService&lt;/code&gt; in the repo: it stacks lanes top to bottom, keeps all lanes the same width, and normalizes &lt;code&gt;order&lt;/code&gt; after a reorder. Each operation is plain arithmetic over &lt;code&gt;getChildren()&lt;/code&gt; (also on &lt;code&gt;NgDiagramModelService&lt;/code&gt;) plus one batched &lt;code&gt;updateNodes&lt;/code&gt; call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live resize
&lt;/h3&gt;

&lt;p&gt;Resizing itself comes for free: the &lt;code&gt;&amp;lt;ng-diagram-node-resize-adornment&amp;gt;&lt;/code&gt; wrapper from the lane template gives the lane its drag handles, and for a standalone node that is all you need. Lanes add extra behavior on top of it – width sync and stacking – and restyle the adornment.&lt;/p&gt;

&lt;p&gt;The diagram template from step 1 binds the &lt;code&gt;(nodeResized)&lt;/code&gt; event: it fires on every tick of a resize gesture, so while the user drags a lane, the other lanes follow live – widths stay equal, the stack stays flush (&lt;code&gt;SwimlaneService.onLaneResized&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Lanes resize from the right and bottom edges only – the rest of the resize adornment is switched off with a small CSS override (a first-class option for this is planned in the adornment API):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* src/app/bpmn/diagram/templates/nodes/swimlane-node/swimlane-node.component.scss */&lt;/span&gt;
&lt;span class="nd"&gt;:host&lt;/span&gt; &lt;span class="nd"&gt;::ng-deep&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.resize-line--top&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nc"&gt;.resize-line--left&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.resize-handle--top-left&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nc"&gt;.resize-handle--top-right&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nc"&gt;.resize-handle--bottom-left&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adornment's look itself is themable – the engine exposes &lt;code&gt;--ngd-resize-*&lt;/code&gt; variables, and the app points them at its brand tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* src/styles.scss */&lt;/span&gt;
&lt;span class="cm"&gt;/* Engine bridge – point ngDiagram's themable tokens at the brand. */&lt;/span&gt;
&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;--ngd-resize-line-border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;c-brand&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="na"&gt;--ngd-resize-handle-background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;c-surface-2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="na"&gt;--ngd-resize-handle-border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;c-brand&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="na"&gt;--ngd-resize-handle-border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In the app:&lt;/strong&gt; drop a task from the palette into a lane – it becomes the lane's child. Then drag it over another lane – that lane highlights, and releasing moves the node into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7 – editor rules live in the config
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;NgDiagramConfig&lt;/code&gt; is the engine's central settings object, passed once through the &lt;code&gt;[config]&lt;/code&gt; binding from step 1. Its options are grouped by feature – &lt;code&gt;linking&lt;/code&gt;, &lt;code&gt;grouping&lt;/code&gt;, &lt;code&gt;resize&lt;/code&gt;, &lt;code&gt;edgeRouting&lt;/code&gt;, &lt;code&gt;zoom&lt;/code&gt;, &lt;code&gt;snapping&lt;/code&gt;, and more – and most of them are plain values or small callbacks the engine consults during gestures.&lt;/p&gt;

&lt;p&gt;This editor uses 4 of those groups to enforce its BPMN rules: which elements may connect, what type a drawn edge gets, how small a lane may get, and how edges are routed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/diagram/diagram-config.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildDiagramConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;laneMinSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lane&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;NgDiagramConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;edgeRouting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;defaultRouting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orthogonal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Never let a lane (or any node) shrink smaller than the elements it holds.&lt;/span&gt;
      &lt;span class="na"&gt;allowResizeBelowChildrenBounds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;getMinNodeSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Size&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;isSwimlane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;laneMinSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;grouping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Only real flow elements may be grouped, and only into a swimlane.&lt;/span&gt;
      &lt;span class="na"&gt;canGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;isSwimlane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isSwimlane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;linking&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Swimlanes cannot be connection endpoints.&lt;/span&gt;
      &lt;span class="na"&gt;validateConnection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;_sourcePort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Port&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isSwimlane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isSwimlane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="c1"&gt;// Every user-drawn connection becomes a typed sequence flow.&lt;/span&gt;
      &lt;span class="na"&gt;finalEdgeDataBuilder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Edge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BPMN_EDGE_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orthogonal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BpmnEdgeKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sequence&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Edge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;NgDiagramConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diagram component builds this config once, handing it the lane minimum-size callback from &lt;code&gt;SwimlaneService&lt;/code&gt;, and binds it to &lt;code&gt;[config]&lt;/code&gt; from step 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/diagram/diagram.component.ts&lt;/span&gt;
&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildDiagramConfig&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;lane&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;swimlanes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;laneMinSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lane&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key line is &lt;code&gt;finalEdgeDataBuilder&lt;/code&gt;: every user-drawn connection becomes a typed sequence flow, so an untyped edge cannot exist. The model from step 2 is enforced at the gesture level.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;laneMinSize&lt;/code&gt; computes the width floor from all lanes' content: lanes share the same width, so shrinking one lane must not push another lane's content out of bounds. Height is set per lane.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the app:&lt;/strong&gt; connect 2 tasks – the edge is created as a solid sequence flow with an arrowhead. Then try to shrink a lane below its content – the resize stops at the minimum size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8 – auto-layout with ELK, per lane
&lt;/h2&gt;

&lt;p&gt;ngDiagram ships no built-in auto-layout – you bring your own engine. This project uses &lt;a href="https://github.com/kieler/elkjs" rel="noopener noreferrer"&gt;elkjs&lt;/a&gt; with the &lt;code&gt;layered&lt;/code&gt; algorithm and &lt;code&gt;RIGHT&lt;/code&gt; direction, because BPMN reads left to right.&lt;/p&gt;

&lt;p&gt;The Layout button in the toolbar calls &lt;code&gt;SwimlaneService.runLayout()&lt;/code&gt;. It lays out each lane's children with ELK – only the sequence flows inside that lane count, so cross-lane message flows never distort the arrangement – then a pure helper turns the results into lane frames and child positions: every lane gets the width of the widest content, and the lanes stack to fit. Everything lands in one batched &lt;code&gt;updateNodes&lt;/code&gt; call, and the viewport stays where the user left it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/bpmn/diagram/swimlanes/swimlane.service.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;runLayout&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lanes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lanes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lanes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;layoutEdges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;isLayoutEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;BpmnEdge&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Layout each lane's children independently (cross-lane edges ignored).&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;perLane&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;lanes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lane&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;childrenOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lane&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isBpmnElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;layoutLane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;layoutEdges&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;lane&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;computeAutoLayoutUpdates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;perLane&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The details live next to it in the repo: &lt;code&gt;swimlanes/lane-auto-layout.ts&lt;/code&gt; computes the updates, and &lt;code&gt;layout/elk-layout.service.ts&lt;/code&gt; wraps the ELK call.&lt;/p&gt;

&lt;p&gt;If you would rather show the whole diagram after a layout instead of leaving the viewport alone, wrap the write in a transaction that waits for re-measurement and call &lt;code&gt;zoomToFit&lt;/code&gt; after it – it needs the new bounds, not the pre-layout ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example – NgDiagramService.transaction + NgDiagramViewportService.zoomToFit.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ngDiagram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;computeAutoLayoutUpdates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;perLane&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitForMeasurements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zoomToFit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In the app:&lt;/strong&gt; scatter nodes across lanes and click Layout in the toolbar. Each lane arranges left to right, and all lanes share the same width.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9 – theming with CSS tokens
&lt;/h2&gt;

&lt;p&gt;The whole editor themes from one attribute: &lt;code&gt;data-theme&lt;/code&gt; on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;. Two sets of CSS variables react to it – the app's own design tokens and ngDiagram's &lt;code&gt;--ngd-*&lt;/code&gt; tokens, which ship with a light and a dark theme built in. A small &lt;code&gt;ThemeService&lt;/code&gt; writes the attribute and persists the choice; the toolbar button toggles it (see &lt;a href="https://github.com/synergycodes/bpmn-editor" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The app tokens live in &lt;code&gt;src/tokens.css&lt;/code&gt;. Components use semantic names, and each theme block points those names at different colors – flipping the attribute recolors every shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* src/tokens.css (fragment) */&lt;/span&gt;
&lt;span class="nd"&gt;:root&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;'light'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--c-bpmn-node-fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--prim-gray-100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--c-bpmn-node-stroke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--prim-gray-600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;'dark'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--c-bpmn-node-fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--prim-gray-700&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--c-bpmn-node-stroke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--prim-gray-450&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine's canvas, selection ring, and default node colors theme themselves the same way. To restyle them, override the &lt;code&gt;--ngd-*&lt;/code&gt; variables – this project keeps the engine defaults apart from the resize-adornment variables from step 6; an override looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Example – e.g. in src/styles.scss. Point engine tokens at your palette. */&lt;/span&gt;
&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;'dark'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--ngd-diagram-background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--c-canvas&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--ngd-node-stroke-primary-hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--c-brand&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In the app:&lt;/strong&gt; flip the theme with the toolbar button. Canvas, lanes, shapes, and edges change together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;That is the whole editor – and look at what we did not have to build: drag and drop, coordinate math, model state, selection, resizing, grouping, edge routing. ngDiagram did the heavy, tedious work, so the editor's ~1,700 lines are almost entirely business logic: BPMN types, lane geometry, editor rules. And where a default did not fit, we overrode exactly the piece we needed – a config callback, a CSS variable, a template. The library is not a black box; it is built to be configured and changed.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/synergycodes/bpmn-editor" rel="noopener noreferrer"&gt;repo&lt;/a&gt; is a working starting template: swap the model file and the templates, and the same building blocks give you an org chart or a pipeline editor instead of BPMN.&lt;/p&gt;

&lt;p&gt;If this saved you time, star ng-diagram on &lt;a href="https://github.com/synergycodes/ng-diagram" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When to pick a different library
&lt;/h2&gt;

&lt;p&gt;Use bpmn-js when you need standard BPMN 2.0 XML interchange. Use GoJS or JointJS when diagrams grow very large – they render to canvas or plain SVG, which scales further than a component per node. For an embedded, branded editor inside an Angular product, ngDiagram is the direct fit.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;ngDiagram&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;bpmn-js&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;GoJS&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;JointJS&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BPMN out of the box&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No – you model it yourself&lt;/td&gt;
&lt;td&gt;Yes – full BPMN 2.0 XML round-trip&lt;/td&gt;
&lt;td&gt;No – samples, not a product&lt;/td&gt;
&lt;td&gt;Shapes in the paid tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Angular integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Native – components, signals, typed outputs&lt;/td&gt;
&lt;td&gt;No official wrapper – you own the bridge&lt;/td&gt;
&lt;td&gt;Official thin wrapper (&lt;code&gt;gojs-angular&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;No official Angular wrapper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nodes rendered as&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Angular components in the DOM&lt;/td&gt;
&lt;td&gt;SVG via custom renderers&lt;/td&gt;
&lt;td&gt;Canvas scene-graph objects&lt;/td&gt;
&lt;td&gt;SVG with its own model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Licensing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Apache 2.0; watermark off via config&lt;/td&gt;
&lt;td&gt;Free, but license-mandated watermark&lt;/td&gt;
&lt;td&gt;Commercial, per developer&lt;/td&gt;
&lt;td&gt;Open core; advanced tier paid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best when…&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Branded editor in an Angular product&lt;/td&gt;
&lt;td&gt;You need &lt;code&gt;.bpmn&lt;/code&gt; XML files in and out&lt;/td&gt;
&lt;td&gt;Large-scale diagrams&lt;/td&gt;
&lt;td&gt;Large-scale diagrams&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For scale, keep the numbers in view: real-world BPMN models run dozens to hundreds of elements, not tens of thousands. ngDiagram handles hundreds of nodes without tuning. For larger diagrams, it documents viewport virtualization: only elements near the viewport render to the DOM.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can ngDiagram import or export BPMN 2.0 XML?&lt;/strong&gt;&lt;br&gt;
Not out of the box. The model round-trips as JSON (&lt;code&gt;toJSON()&lt;/code&gt; / &lt;code&gt;initializeModel&lt;/code&gt;), and you can write a &lt;code&gt;.bpmn&lt;/code&gt; mapping on top of the typed model when you need it. If standard XML interchange is the core requirement, use bpmn-js.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many elements can a DOM-rendered diagram handle?&lt;/strong&gt;&lt;br&gt;
Hundreds of nodes work without tuning, and ngDiagram documents viewport virtualization for larger models. Real-world BPMN diagrams rarely exceed a few hundred elements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do users draw connections between elements?&lt;/strong&gt;&lt;br&gt;
They drag between ports. A config function (&lt;code&gt;validateConnection&lt;/code&gt;) checks each connection and blocks lanes from being endpoints. A second function (&lt;code&gt;finalEdgeDataBuilder&lt;/code&gt;) turns every drawn connection into a typed sequence flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can the ngDiagram watermark be removed?&lt;/strong&gt;&lt;br&gt;
Yes. The documented &lt;code&gt;hideWatermark: true&lt;/code&gt; config disables it. ngDiagram is Apache 2.0, so this is allowed by the license – unlike bpmn-js, whose license requires its watermark to stay visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is ngDiagram and who maintains it?&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/synergycodes/ng-diagram" rel="noopener noreferrer"&gt;ngDiagram&lt;/a&gt; is an open-source Angular diagramming library built from scratch by &lt;a href="https://www.synergycodes.com" rel="noopener noreferrer"&gt;Synergy Codes&lt;/a&gt; and released under Apache 2.0. It provides composable building blocks – nodes, edges, groups – with native Angular patterns: components, services, dependency injection, and signal-based change detection. It targets production use with hundreds of nodes: workflow editors, org charts, industrial schematics, network topologies. Full guides live at &lt;a href="https://www.ngdiagram.dev/docs" rel="noopener noreferrer"&gt;ngdiagram.dev/docs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I've Used GoJS for Years. Here's What Happened When I Built an Org Chart with ngDiagram</title>
      <dc:creator>Wojciech Krzesaj</dc:creator>
      <pubDate>Tue, 14 Apr 2026 07:59:11 +0000</pubDate>
      <link>https://dev.to/ngdiagram-dev/ive-used-gojs-for-years-heres-what-happened-when-i-built-an-org-chart-with-ngdiagram-1gkn</link>
      <guid>https://dev.to/ngdiagram-dev/ive-used-gojs-for-years-heres-what-happened-when-i-built-an-org-chart-with-ngdiagram-1gkn</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I've spent 5+ years building diagram-heavy apps with GoJS. Recently I built an org chart starter kit with ngDiagram - an Angular-native, open-source diagramming library. The big difference: nodes are just Angular components, styling is just CSS, and the MCP server makes AI assistance actually useful during development. The trade-off is real though - you build more yourself (layouts or expand/collapse), but you get full control and zero context-switching between "diagram code" and "Angular code." If you're an Angular dev evaluating diagramming options, this is an honest breakdown of both. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; I work at Synergy Codes, the company behind ngDiagram. This is my honest experience - including the rough edges. &lt;/p&gt;

&lt;p&gt;Picking a diagramming library for your project is a bigger decision than it looks. It's not just "can it render nodes and edges" - it's how it fits into your stack, how it scales with your requirements, and how productive you are day to day while working with it. The right choice helps you develop and introduce new features faster. &lt;/p&gt;

&lt;h2&gt;
  
  
  I've Used GoJS for Five Years. Three Weeks with ngDiagram Changed My Perspective
&lt;/h2&gt;

&lt;p&gt;I work as a Software Developer at &lt;a href="https://synergycodes.com/" rel="noopener noreferrer"&gt;Synergy Codes&lt;/a&gt; (the company behind ngDiagram), where I've been building diagram-heavy applications for over 5 years. Most of my experience is with GoJS, but I've worked with other diagramming libraries too. The projects spanned industries from automotive manufacturing and AI-powered tools to engineering platforms and enterprise analytics.  &lt;/p&gt;

&lt;p&gt;GoJS is powerful, no question. But it always felt like bolting a separate application onto your project. Recently, I built an org chart starter kit with &lt;a href="https://ngdiagram.dev/" rel="noopener noreferrer"&gt;ngDiagram&lt;/a&gt;, an Angular-native library that rethinks the whole approach. With all that background, I had a lot of context for what usually goes wrong and what usually works.  &lt;/p&gt;

&lt;p&gt;Here's what I found after about 3 weeks of building. &lt;br&gt;
 &lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37in7s7glmza1bnsg1p4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37in7s7glmza1bnsg1p4.png" width="800" height="450"&gt;&lt;/a&gt;Bird's-eye view of the org chart starter kit built with ngDiagram&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Brief: What This Org Chart Had to Do
&lt;/h2&gt;

&lt;p&gt;The goal: build an interactive org chart to showcase ngDiagram's capabilities. Not a static tree, but a fully interactive product. &lt;/p&gt;

&lt;p&gt;It needed to: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display employees in a hierarchy with editable properties. &lt;/li&gt;
&lt;li&gt;Support drag &amp;amp; drop to restructure reporting lines. &lt;/li&gt;
&lt;li&gt;Render in both horizontal and vertical tree layouts. &lt;/li&gt;
&lt;li&gt;Show different node variants depending on zoom level - full details when zoomed in, compact view when zoomed out. &lt;/li&gt;
&lt;li&gt;Switch nodes between occupied and vacant dynamically. Clear someone's name in the sidebar, and the node becomes a placeholder. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sidebar form was wired to the diagram in real-time, including a dropdown that actually re-parented nodes in the graph. The whole tree supported expand/collapse of child nodes. &lt;/p&gt;

&lt;p&gt;I've built similar projects in GoJS before. This time I was genuinely curious how the whole experience would turn out with ngDiagram. &lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started: Less to Learn Than I Expected
&lt;/h2&gt;

&lt;p&gt;The first thing that struck me was how little I had to relearn. If you know Angular, you already know most of what you need to get started with ngDiagram. Node templates are just Angular components. Styling is just CSS. The diagram integrates into your project like any other piece of your UI. &lt;/p&gt;

&lt;p&gt;That's a real contrast to GoJS, where the diagram code tends to live in its own world. You build UI using &lt;code&gt;GraphObject.make&lt;/code&gt; calls, nesting panels inside panels to construct your nodes. It's a completely different way of building UI than what you're used to in Angular. You have to switch into that mindset every time you touch the diagram. With ngDiagram, I was moving between diagram components and the rest of the app without any context switching. It's all just Angular. &lt;/p&gt;

&lt;p&gt;Within the first few hours, I had a basic diagram rendering with custom, complex nodes. I couldn't say the same about my first encounter with GoJS years ago. &lt;/p&gt;
&lt;h2&gt;
  
  
  The MCP Advantage: AI That Actually Understands the Library
&lt;/h2&gt;

&lt;p&gt;Here's something I didn't expect to write about in a diagramming library review: AI made a huge difference. &lt;/p&gt;

&lt;p&gt;ngDiagram ships with an MCP server, which means AI assistants understand the library's API and patterns. I used AI throughout the entire project. I was designing architecture, making technical decisions, and guiding the AI on how things should work. What I didn't do was dive deep into ngDiagram-specific API details - I relied on the AI's knowledge of the library for that, and the results were solid. &lt;/p&gt;

&lt;p&gt;I'd describe the behavior I wanted, explain the technical approach, sometimes show mockups - and get working code back in a few iterations. Node templates in particular were written almost entirely by AI. I reviewed them, fixed minor bugs, and moved on. &lt;/p&gt;

&lt;p&gt;This works so well because ngDiagram's building blocks are standard Angular. The AI isn't wrestling with some custom API - it's generating components and CSS. Things it already excels at. &lt;/p&gt;
&lt;h2&gt;
  
  
  Building the Org Chart with ngDiagram
&lt;/h2&gt;

&lt;p&gt;The org chart touched several areas of ngDiagram's API - from node templates and layout integration to drag &amp;amp; drop, expand/collapse, and edge connections. Here's how each one played out - and where it felt different from what I was used to in GoJS. &lt;/p&gt;
&lt;h3&gt;
  
  
  Node Templates: Three States, One Component
&lt;/h3&gt;

&lt;p&gt;The org chart needed three visual states for each node. A full view of all employee details. A compact view at lower zoom levels. A vacant state for unassigned positions. Some elements were independent of the visual state - the expand/collapse button shows when a node has children, and the add-node buttons appear on hover. Their logic lives outside the variant switching entirely. &lt;br&gt;
 &lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftds31l3tb9bymwa0q7jl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftds31l3tb9bymwa0q7jl.png" alt="Screenshot of the Org chart app - 3 states of node" width="800" height="200"&gt;&lt;/a&gt;Full, compact, and vacant - the three visual states of an org chart node built with ngDiagram &lt;p&gt;&lt;/p&gt;

&lt;p&gt;In ngDiagram, this was one Angular component with a &lt;code&gt;@switch&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@switch (variant()) {
  @case ('vacant') {
    &lt;span class="nt"&gt;&amp;lt;app-vacant-node&lt;/span&gt; &lt;span class="na"&gt;[role]=&lt;/span&gt;&lt;span class="s"&gt;"node().data.role"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  }
  @case ('compact') {
    @let data = occupiedData();
    &lt;span class="nt"&gt;&amp;lt;app-compact-node&lt;/span&gt;
      &lt;span class="na"&gt;[fullName]=&lt;/span&gt;&lt;span class="s"&gt;"data?.fullName"&lt;/span&gt;
      &lt;span class="na"&gt;[role]=&lt;/span&gt;&lt;span class="s"&gt;"data?.role"&lt;/span&gt;
      &lt;span class="na"&gt;[color]=&lt;/span&gt;&lt;span class="s"&gt;"color()"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  }
  @case ('full') {
    @let data = occupiedData();
    &lt;span class="nt"&gt;&amp;lt;app-full-node&lt;/span&gt;
      &lt;span class="na"&gt;[fullName]=&lt;/span&gt;&lt;span class="s"&gt;"data?.fullName"&lt;/span&gt;
      &lt;span class="na"&gt;[role]=&lt;/span&gt;&lt;span class="s"&gt;"data?.role"&lt;/span&gt;
      &lt;span class="na"&gt;[color]=&lt;/span&gt;&lt;span class="s"&gt;"color()"&lt;/span&gt;
      &lt;span class="na"&gt;[reports]=&lt;/span&gt;&lt;span class="s"&gt;"data?.reports"&lt;/span&gt;
      &lt;span class="na"&gt;[span]=&lt;/span&gt;&lt;span class="s"&gt;"data?.span"&lt;/span&gt;
      &lt;span class="na"&gt;[shiftCapacity]=&lt;/span&gt;&lt;span class="s"&gt;"data?.shiftCapacity"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The zoom level comes from a diagram service as a signal. One &lt;code&gt;computed()&lt;/code&gt; drives the whole switch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;computed&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NodeVariant&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isVacantNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;node&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vacant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewportService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;full&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shared elements - expand/collapse, add buttons - just live outside the &lt;code&gt;@switch&lt;/code&gt;. Their own conditions determine when they show, completely independent of which variant is active. &lt;/p&gt;

&lt;p&gt;Nodes can also switch between occupied and vacant dynamically. Clear an employee's name in the sidebar form and the node converts to vacant live. Same node, different identity - no re-creation needed. &lt;/p&gt;

&lt;p&gt;In GoJS, you'd typically handle this with category switching. That means managing separate templates for each visual state - the behavior spreads across multiple definitions instead of being visible in one place. Or you could use visibility bindings on nested panels, which gets cumbersome when you have many dependent elements. Those shared elements? You can reuse them, but you still have to include them in every template. &lt;/p&gt;

&lt;h3&gt;
  
  
  Layout: Plugging In ELK.js
&lt;/h3&gt;

&lt;p&gt;For the tree layout, I used ELK.js with its &lt;code&gt;mrtree&lt;/code&gt; algorithm. The integration was straightforward - compute positions with ELK, assign them back to ngDiagram nodes. Both horizontal and vertical orientations worked. Coordinating ngDiagram transactions with ELK's async layout took some effort - but it was a solvable problem. &lt;br&gt;
 &lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljohyvtzmdpznlxbha07.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljohyvtzmdpznlxbha07.gif" alt="Switching between horizontal and vertical tree layouts GIF" width="800" height="450"&gt;&lt;/a&gt;Switching between horizontal and vertical tree layouts - powered by ELK.js mrtree algorithm&lt;p&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Drag &amp;amp; Drop: Custom Implementation, Solid Building Blocks
&lt;/h3&gt;

&lt;p&gt;Drag &amp;amp; drop was more interesting. Beyond simple repositioning, I needed drops near existing nodes to trigger hierarchy changes - reassigning an employee's reporting line. This required a fully custom implementation, but ngDiagram provided useful building blocks - API methods and events - that helped put it together. The building blocks were there - I just had to put them together. &lt;br&gt;
 &lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyn3llxguyqhbgoswg9ao.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyn3llxguyqhbgoswg9ao.gif" alt="Restructuring the hierarchy with drag &amp;amp; drop GIF" width="800" height="450"&gt;&lt;/a&gt;Restructuring the hierarchy with drag &amp;amp; drop - a node moved to a new position&lt;p&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Expand / Collapse: Not Built In - But Not Hard Either
&lt;/h3&gt;

&lt;p&gt;Expand/collapse was where the difference in library maturity showed most clearly. In GoJS, tree expand/collapse is built in - you get it essentially for free. In ngDiagram, you manage node visibility yourself by setting &lt;code&gt;isHidden&lt;/code&gt; on individual nodes, which means writing your own tree traversal logic to hide or show entire subtrees. It's not hard, but it's work that GoJS handles out of the box. &lt;br&gt;
 &lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7f9ohi24aezjiqp43q2x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7f9ohi24aezjiqp43q2x.gif" alt="Expanding and collapsing child nodes GIF" width="800" height="450"&gt;&lt;/a&gt;Expanding and collapsing child nodes in the ngDiagram org chart&lt;p&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ports: Explicit by Design
&lt;/h3&gt;

&lt;p&gt;In GoJS, any object on a node can be a port. In ngDiagram, ports are explicit components - you place &lt;code&gt;&amp;lt;ng-diagram-port&amp;gt;&lt;/code&gt; elements in your template and configure their position and type. In this project, I used two: top for incoming edges, bottom for outgoing. &lt;/p&gt;

&lt;p&gt;It's a more structured approach. Different from what I knew, but it made the connection points very clear in the template. &lt;/p&gt;

&lt;h3&gt;
  
  
  Theming: Light, Dark, and Zero Extra Wiring
&lt;/h3&gt;

&lt;p&gt;I used CSS custom properties as design tokens throughout the entire application - colors, spacing, borders, shadows, everything. One token file with definitions for light and dark themes. A toggle switches &lt;code&gt;data-theme&lt;/code&gt; on the document root, and the whole app - diagram nodes, sidebar, buttons, backgrounds - updates instantly. &lt;/p&gt;

&lt;p&gt;Because ngDiagram nodes are just Angular components with regular CSS, they pick up the tokens automatically. No special integration, no theme service wiring into the diagram. The same tokens that style a sidebar button also style a node's header. Once the token system was in place, adding the dark theme was trivial - just a new set of values. &lt;br&gt;
 &lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bl7k363hg1r5n9bsbfg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bl7k363hg1r5n9bsbfg.gif" alt="Switching between light and dark mode in the org chart GIF" width="800" height="450"&gt;&lt;/a&gt;Switching between light and dark mode in the ngDiagram org chart&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  GoJS vs. ngDiagram: An Honest Comparison
&lt;/h2&gt;

&lt;p&gt;After three weeks of building with ngDiagram and years of building with GoJS, here's where I think each one stands. &lt;/p&gt;

&lt;h3&gt;
  
  
  Where GoJS Excels
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Feature Set and Maturity
&lt;/h4&gt;

&lt;p&gt;GoJS has years of development behind it. The feature set is massive - built-in tools for most common diagramming needs and countless edge cases already handled. Documentation is extensive, and there's an answer to almost every question. If you need a complex, battle-tested solution for a large enterprise project, GoJS delivers. &lt;/p&gt;

&lt;h4&gt;
  
  
  Considerations
&lt;/h4&gt;

&lt;p&gt;That said, GoJS can feel like an oversized tool for some projects. You carry a lot of weight you may never use. If you're new to the library, the learning curve is steep - it takes time before you can use it to its full potential. Licensing is worth mentioning too: GoJS requires a paid commercial license. It's justified for large projects, but it's a real consideration. &lt;/p&gt;

&lt;h3&gt;
  
  
  Where ngDiagram Excels
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Feature Set and Core
&lt;/h4&gt;

&lt;p&gt;ngDiagram gives you a solid foundation - node and edge rendering with full Angular templates, edge drawing between ports, selection, a minimap with zoom controls, viewport management, and a signals-based reactive model wrapped in transactions. That's the core, and it works well out of the box. &lt;/p&gt;

&lt;h4&gt;
  
  
  Developer Experience
&lt;/h4&gt;

&lt;p&gt;On the developer experience side, ngDiagram has a lot going for it. If you're an Angular developer, the onboarding is remarkably smooth. You're writing components, using HTML, working with signals or connecting to your state management.  &lt;/p&gt;

&lt;p&gt;The styling story is strong too - achieving polished visual results feels natural with CSS. Theming is a perfect example: I built a full light/dark theme using CSS design tokens, and the same tokens style both the diagram nodes and the rest of the app. One toggle, everything updates. In GoJS, theming was recently introduced, but it uses a separate object-based system rather than CSS - which makes it harder to keep the diagram visually consistent with the rest of your Angular application.  &lt;/p&gt;

&lt;p&gt;AI assistance via the MCP server also deserves special mention - it works exceptionally well, and the MCP integration makes AI genuinely useful throughout development. &lt;/p&gt;

&lt;h4&gt;
  
  
  Extensibility and Integration
&lt;/h4&gt;

&lt;p&gt;ngDiagram is also genuinely extensible. The API provides building blocks - methods, events, services - that let you build custom functionality on top without fighting the library. It's not just for small projects. It handles large numbers of nodes with strong performance and can power complex applications even without the years of maturity that GoJS has. &lt;/p&gt;

&lt;p&gt;There's also the integration story. In GoJS projects, the diagram always felt like a separate part of the application. The way you use it is so different from the rest of your code that it's natural to think of it as its own thing. ngDiagram dissolves that boundary. It's Angular all the way down. &lt;a href="https://github.com/synergycodes/ng-diagram" rel="noopener noreferrer"&gt;And it's open source&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-off: What ngDiagram Asks of You
&lt;/h2&gt;

&lt;p&gt;ngDiagram's philosophy isn't to ship every feature built in. It gives you building blocks and a clean API to extend them. The tree layout, for example, isn't part of ngDiagram itself - you bring your own layout engine. I used &lt;a href="https://github.com/kieler/elkjs" rel="noopener noreferrer"&gt;ELK.js&lt;/a&gt; with the &lt;code&gt;mrtree&lt;/code&gt; algorithm, and ngDiagram provides &lt;a href="https://www.ngdiagram.dev/docs/examples/layout-integration/" rel="noopener noreferrer"&gt;a documented example of this integration&lt;/a&gt;, so I wasn't starting from scratch.  &lt;/p&gt;

&lt;p&gt;Things you build on top of what the library gives you: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expand/collapse (you manage node visibility via &lt;code&gt;isHidden&lt;/code&gt;) &lt;/li&gt;
&lt;li&gt;Zoom-responsive node variants &lt;/li&gt;
&lt;li&gt;Hierarchy validation &lt;/li&gt;
&lt;li&gt;Re-parenting logic &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That approach means more upfront work compared to something like GoJS, where tree layouts and expand/collapse come ready-made. But it also means you're never fighting someone else's assumptions about how your feature should work. The custom drag &amp;amp; drop with drop indicators, the anti-cycle validation, the way nodes switch between occupied and vacant - all of these fit naturally because I built them with standard Angular patterns on top of ngDiagram's API, not around a pre-built behavior. &lt;/p&gt;

&lt;h3&gt;
  
  
  There were a couple of rough edges along the way
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Expand/collapse positioning:&lt;/strong&gt; When expanding a collapsed subtree, the child nodes would reappear at their last known positions - scattered across the canvas - instead of emerging from the parent. The fix was to set all children's positions to the parent's coordinates before triggering the re-layout. That way they animate out from where you'd expect. A small thing, but the kind of detail that matters for a polished result. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watermark positioning:&lt;/strong&gt; I also ran into a watermark positioning issue where the default placement overlapped with my sidebar UI. The workaround was a &lt;code&gt;::ng-deep&lt;/code&gt; override - not ideal, but functional. The ngDiagram team already has an open GitHub issue for a proper positioning API, so this one is temporary. &lt;/p&gt;

&lt;p&gt;None of these were blockers. The API always gave me enough to solve the problem. But if you're coming from a batteries-included library like GoJS, it's worth knowing that ngDiagram asks you to bring more of your own implementation - and in return, you get full control over how everything works. &lt;/p&gt;

&lt;p&gt;As for delivery speed - it's hard to compare directly. I had more GoJS experience, but less practice using AI with it. With ngDiagram, it was the opposite: less library knowledge, but excellent AI support. It roughly evened out. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Verdict
&lt;/h2&gt;

&lt;p&gt;Working with ngDiagram felt natural. The AI support through MCP was outstanding, and the fact that I could lean on Angular patterns I already knew made the whole experience smooth. After a while, things clicked and I could move fast. By the end of the project, ngDiagram had earned a permanent spot in my toolbox. &lt;/p&gt;

&lt;p&gt;If you're building in Angular, ngDiagram is a natural fit. The onboarding is fast. The MCP server makes AI assistance genuinely useful. The extensible APIs let you build what you need. CSS-based styling lets you achieve polished results naturally. It's a strong fit when you want full control over your diagramming features, and you're comfortable building on top of a solid core. &lt;/p&gt;

&lt;p&gt;GoJS is a strong fit for different reasons. If your project needs a wide range of built-in interactions, layout algorithms, and tools ready to go from day one - or if you're working across multiple frameworks - GoJS delivers that. The documentation is deep, the community is established, and for large enterprise projects with complex requirements, that maturity is genuinely valuable. &lt;/p&gt;

&lt;p&gt;The question is whether you'd rather build on a flexible core with Angular-native DX, or start with a comprehensive toolkit and adapt it to your stack. &lt;/p&gt;

&lt;p&gt;ngDiagram's architecture is solid, and the library is still growing. The potential is there. &lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Documentation: &lt;a href="https://www.ngdiagram.dev/docs" rel="noopener noreferrer"&gt;https://www.ngdiagram.dev/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/synergycodes/ng-diagram" rel="noopener noreferrer"&gt;https://github.com/synergycodes/ng-diagram&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NPM: &lt;a href="https://www.npmjs.com/package/ng-diagram" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/ng-diagram&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Website: &lt;a href="https://www.ngdiagram.dev" rel="noopener noreferrer"&gt;https://www.ngdiagram.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
