Custom Middleware Example
This example demonstrates how to create a custom middleware that implements read-only functionality for ngDiagram. Middlewares provide a powerful plugin architecture for extending diagram behavior by intercepting state changes before they reach the model.
import '@angular/compiler';import { Component } from '@angular/core';import { provideNgDiagram } from 'ng-diagram';import { DiagramComponent } from './diagram.component';
/** * Wrapper component that provides the ng-diagram-context */@Component({ imports: [DiagramComponent], providers: [provideNgDiagram()], template: ` <diagram /> `, styleUrl: './diagram-wrapper.component.scss',})export class DiagramWrapperComponent {}import { Component, inject, signal } from '@angular/core';import { createMiddlewares, initializeModel, NgDiagramBackgroundComponent, NgDiagramComponent, NgDiagramService, type NgDiagramConfig,} from 'ng-diagram';import { readOnlyMiddleware } from './read-only-middleware';
@Component({ selector: 'diagram', imports: [NgDiagramComponent, NgDiagramBackgroundComponent], styleUrl: './diagram.component.scss', templateUrl: './diagram.component.html',})export class DiagramComponent { private readonly ngDiagram = inject(NgDiagramService);
isReadOnly = signal(false);
middlewares = createMiddlewares((defaults) => [ readOnlyMiddleware, ...defaults, ]);
config = { zoom: { max: 3, zoomToFit: { onInit: true, padding: 120, }, }, readOnly: { enabled: false, allowedActions: ['changeSelection'], }, } satisfies NgDiagramConfig & { readOnly: any };
model = initializeModel({ nodes: [ { id: '1', position: { x: 100, y: 80 }, data: { label: 'Node 1' }, resizable: true, }, { id: '2', position: { x: 300, y: 80 }, data: { label: 'Node 2' }, resizable: true, }, { id: '3', position: { x: 200, y: 220 }, data: { label: 'Node 3' }, resizable: true, }, ], edges: [ { id: 'e1-2', source: '1', target: '2', sourcePort: 'port-right', targetPort: 'port-left', data: {}, }, { id: 'e2-3', source: '2', target: '3', sourcePort: 'port-right', targetPort: 'port-left', data: {}, }, ], });
toggleReadOnly() { this.isReadOnly.update((current) => { const newValue = !current; this.ngDiagram.updateConfig({ readOnly: { enabled: newValue, allowedActions: ['changeSelection'], }, } as any); return newValue; }); }}import type { Middleware } from 'ng-diagram';
// Define actions that should be blocked in read-only modeconst blockedActions = new Set([ 'changeSelection', 'moveNodesBy', 'deleteSelection', 'addNodes', 'updateNode', 'updateNodes', 'deleteNodes', 'clearModel', 'addEdges', 'updateEdge', 'deleteEdges', 'deleteElements', 'paste', 'resizeNode', 'startLinking', 'moveTemporaryEdge', 'finishLinking', 'changeZOrder', 'rotateNodeTo', 'highlightGroup', 'highlightGroupClear', 'treeLayout', 'moveNodes', 'moveNodesStop',]);
/** * Read-only middleware implementation that blocks specific actions when enabled */export const readOnlyMiddleware: Middleware<'read-only'> = { name: 'read-only', execute: (context, next, cancel) => { const { modelActionType, config } = context;
// Get read-only configuration const readOnlyConfig = (config as any).readOnly;
if (!readOnlyConfig?.enabled) { next(); // Not in read-only mode, allow everything return; }
// Allow specific actions if configured const allowedActions = readOnlyConfig.allowedActions || []; const isActionBlocked = blockedActions.has(modelActionType) && !allowedActions.includes(modelActionType);
if (isActionBlocked) { console.warn(`🔒 Action "${modelActionType}" blocked by read-only mode`); cancel(); // Cancel the operation return; }
// Allow the action to proceed next(); },};<div class="diagram-container"> <div class="controls"> <div class="status"> <span [class.readonly]="isReadOnly()"> {{ isReadOnly() ? 'Diagram is locked - try moving nodes!' : 'Diagram is editable - drag nodes around' }} </span> </div> <button [class.active]="isReadOnly()" (click)="toggleReadOnly()" class="readonly-toggle" > {{ isReadOnly() ? '🔒 Read-Only Mode' : '✏️ Edit Mode' }} </button> </div> <div class="not-content diagram"> <ng-diagram [model]="model" [config]="config" [middlewares]="middlewares"> <ng-diagram-background /> </ng-diagram> </div></div>:host { display: flex; flex-direction: column; height: 100%;}
readonly-middleware-inner { display: flex; flex-direction: column; height: 100%; flex: 1;}:host { display: flex; flex-direction: column; height: 100%; flex: 1;}
.diagram-container { position: relative; display: flex; flex-direction: column; width: 100%; height: var(--ng-diagram-height); border: var(--ng-diagram-border);}
.controls { position: absolute; z-index: 1; top: 1rem; right: 1rem; padding: 1rem; display: flex; align-items: center; gap: 1rem; flex-shrink: 0; justify-content: end; background-color: var(--ngd-node-bg-primary-default); border: var(--ng-diagram-border);}
.readonly-toggle { background: var(--sl-color-accent); color: white; border: none; padding: 0.5rem 1rem; margin-top: 0; border-radius: 0.25rem; cursor: pointer; font-weight: 500; transition: all 0.2s ease;
&:hover { background: var(--sl-color-accent-high); }
&.active { background: var(--sl-color-red);
&:hover { background: var(--sl-color-red-high); } }}
.status { font-size: 0.875rem; color: var(--sl-color-text-accent);
.readonly { color: var(--sl-color-red); font-weight: 500; }}
.diagram { margin-top: 0; flex-grow: 1;
ng-diagram { height: 100%; width: 100%; }}Additional Explanation
Section titled “Additional Explanation”Key Concepts
Section titled “Key Concepts”- Action Blocking: Block specific actions based on configuration.
- Allowlist: Permit selective operations through an allowlist.
- Operation Cancellation: Cancel operations to prevent unwanted state changes.
- Logging: Log warnings for blocked actions.