Custom Node Example
This example demonstrates how to create a custom node with your own template and form controls.
import '@angular/compiler';import { ChangeDetectionStrategy, Component } from '@angular/core';import { initializeModel, NgDiagramBackgroundComponent, NgDiagramComponent, NgDiagramNodeTemplateMap, provideNgDiagram, type NgDiagramConfig,} from 'ng-diagram';
import { NodeComponent } from './node/node.component';
enum NodeTemplateType { CustomNodeType = 'customNodeType',}
@Component({ imports: [NgDiagramComponent, NgDiagramBackgroundComponent], providers: [provideNgDiagram()], changeDetection: ChangeDetectionStrategy.OnPush, template: ` <div class="not-content diagram"> <ng-diagram [model]="model" [config]="config" [nodeTemplateMap]="nodeTemplateMap" > <ng-diagram-background /> </ng-diagram> </div> `, styleUrl: './diagram.component.scss',})export class DiagramComponent { nodeTemplateMap = new NgDiagramNodeTemplateMap([ [NodeTemplateType.CustomNodeType, NodeComponent], ]);
config = { zoom: { max: 3, zoomToFit: { onInit: true, padding: 50, }, }, } satisfies NgDiagramConfig;
model = initializeModel({ nodes: [ { id: '1', position: { x: 80, y: 140 }, type: 'customNodeType', data: { name: 'Node 1', description: 'This is Node 1', tooltip: 'Node 1 is a custom node', }, rotatable: true, resizable: false, }, { id: '2', position: { x: 500, y: 0 }, type: 'customNodeType', data: { name: 'Node 2', description: 'This is Node 2', tooltip: 'Node 2 is a custom node', }, rotatable: true, resizable: false, angle: 30, }, ], edges: [ { id: '1', source: '1', target: '2', data: {}, sourcePort: 'port-top', targetPort: 'port-left', sourceArrowhead: 'ng-diagram-arrow', }, ], });}import { Component, input, model } from '@angular/core';import { NgDiagramNodeResizeAdornmentComponent, NgDiagramNodeRotateAdornmentComponent, NgDiagramNodeSelectedDirective, NgDiagramPortComponent, type NgDiagramNodeTemplate, type Node,} from 'ng-diagram';
type CustomDataType = { name: string; description: string; tooltip: string;};
@Component({ imports: [ NgDiagramNodeRotateAdornmentComponent, NgDiagramPortComponent, NgDiagramNodeResizeAdornmentComponent, ], templateUrl: './node.component.html', styleUrls: ['./node.component.scss'], hostDirectives: [ { directive: NgDiagramNodeSelectedDirective, inputs: ['node'] }, ], host: { '[class.ng-diagram-port-hoverable-over-node]': 'true', },})export class NodeComponent implements NgDiagramNodeTemplate<CustomDataType> { text = model<string>(''); node = input.required<Node<CustomDataType>>();
selectedState: string = 'Inactive';
onStateChange(event: Event) { const selectElement = event.target as HTMLSelectElement; this.selectedState = selectElement.value; }}<ng-diagram-node-rotate-adornment /><ng-diagram-node-resize-adornment> <div class="node"> <div class="node-header"> <div> {{ node().data.name }} </div> <div> <span class="chip" [style.backgroundColor]=" selectedState === 'Active' ? 'green' : selectedState === 'Error' ? 'red' : 'gray' " >{{ selectedState }}</span > </div> </div> <div class="node-divider"></div> <div class="node-body" title="{{ node().data.tooltip }}"> {{ node().data.description }} <form> <label for="state-select">State:</label> <select id="state-select" [value]="selectedState" name="state" data-no-drag="true" data-no-pan="true" (change)="onStateChange($event)" > <option value="Active">Active</option> <option value="Inactive">Inactive</option> <option value="Error">Error</option> </select> </form> </div> </div> <ng-diagram-port id="port-left" type="both" side="left" /> <ng-diagram-port id="port-top" type="both" side="top" /> <ng-diagram-port id="port-right" type="both" side="right" /> <ng-diagram-port id="port-bottom" type="both" side="bottom" /></ng-diagram-node-resize-adornment>.diagram { display: flex; height: var(--ng-diagram-height); border: var(--ng-diagram-border);}:host { --mdc-chip-label-text-size: 10px; --mat-form-field-container-vertical-padding: 8px; --mat-form-field-container-height: 30px;
display: flex; width: 100%; height: 100%; border-radius: 10px; border: 1px solid var(--ngd-node-stroke-primary-default); background-color: var(--ngd-node-bg-primary-default);
.node { display: flex; flex-direction: column; padding: 16px 24px; border-radius: 10px;
&-header { font-weight: 600; color: var(--ngd-txt-primary-default); display: flex; justify-content: space-between; vertical-align: middle; align-items: center; justify-items: center;
.chip { display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 400; padding: 5px 6px; border-radius: 4px; color: white; line-height: 140%; } }
&-divider { height: 1px; background-color: var(--ngd-node-stroke-primary-default); margin: 16px -24px; }
&-body { font-size: 12px; cursor: help;
form { margin-top: 4px; display: flex; flex-direction: column;
select { min-width: 200px; padding: 8px 12px; border-radius: 6px; background: var(--ngd-node-bg-primary-default); border: 1px solid var(--ngd-node-stroke-primary-default); font-size: 12px; } } } }}