Skip to content

Transactions

Transactions in ngDiagram provide a mechanism for batching multiple state changes into atomic operation. This is particularly useful for complex operations that involve multiple nodes and edges, ensuring better performance.

The transaction API is straightforward - simply wrap your operations in a callback.

this.ngDiagramService.transaction(() => {
this.ngDiagramModelService.addNodes([node1, node2]);
this.ngDiagramModelService.addEdges([edge1]);
});

Understanding how transactions are applied internally helps you write better code and reason about your diagram state changes. When a transaction is committed, operations are executed in a specific sequence to maintain data integrity:

stateUpdate.nodesToAdd?.forEach((node) => this.addNode(node));
stateUpdate.edgesToAdd?.forEach((edge) => this.addEdge(edge));
stateUpdate.edgesToRemove?.forEach((id) => this.removeEdge(id));
stateUpdate.nodesToRemove?.forEach((id) => this.removeNode(id));
stateUpdate.nodesToUpdate?.forEach((node) => this.updateNode(node));
stateUpdate.edgesToUpdate?.forEach((edge) => this.updateEdge(edge));
if (stateUpdate.metadataUpdate) {
this.metadata = { ...this.metadata, ...stateUpdate.metadataUpdate };
}

This fixed order ensures that:

  • Nodes are added first, making them available before edges that might reference them
  • Edges are added next, after all required nodes exist
  • Edges are removed before nodes, preventing dangling edge references
  • Nodes are removed after edges, ensuring no edges point to non-existent nodes
  • Updates are applied after all additions and removals, ensuring the structure is stable before modifying properties
  • Metadata is merged last, after all other operations are complete

Transactions batch multiple operations together, reducing the number of state updates and re-renders. Instead of triggering change detection for each individual operation, all changes are applied at once.

All operations within a transaction succeed together, preventing partial state updates that could leave your diagram in an inconsistent state.

Use transactions for:

  • Complex multi-step operations - Creating multiple related nodes and edges
  • Bulk operations - Adding or updating many elements at once
  • Performance optimization - Reducing the number of state updates

Group related operations together, but avoid making transactions too large or complex:

// ✅ Good - related operations grouped together
this.ngDiagramService.transaction(() => {
this.ngDiagramModelService.addNodes([node1, node2, node3]);
this.ngDiagramModelService.addEdges([edge1, edge2]);
});
// ❌ Avoid - unrelated operations in same transaction
this.ngDiagramService.transaction(() => {
this.ngDiagramModelService.addNodes([...]);
this.updateUserPreferences(); // Unrelated operation
this.updateDiagramName(); // Another unrelated operation
});

Wrap the entire loop in a single transaction instead of starting one for every iteration to minimize overhead

// ✅ Good - loop inside transaction
this.ngDiagramService.transaction(() => {
for (const node of nodes) {
this.ngDiagramModelService.updateNodeData(node);
}
});
// ❌ Avoid - transactions inside a loop
for (const node of nodes) {
this.ngDiagramService.transaction(() => {
this.ngDiagramModelService.updateNodeData(node); // Each iteration creates a separate transaction
});
}

Read more about global configuration in ngDiagram →

import '@angular/compiler';
import { Component, inject } from '@angular/core';
import {
initializeModel,
NgDiagramBackgroundComponent,
NgDiagramComponent,
NgDiagramModelService,
NgDiagramService,
provideNgDiagram,
type NgDiagramConfig,
} from 'ng-diagram';
@Component({
imports: [NgDiagramComponent, NgDiagramBackgroundComponent],
providers: [provideNgDiagram()],
template: `
<div class="toolbar">
<button (click)="onTestTransactionClick()">
Create diagram (Transaction)
</button>
<button (click)="onTestWithoutTransactionClick()">
Create diagram (Without Transaction)
</button>
</div>
<div class="not-content diagram">
<ng-diagram [model]="model" [config]="config">
<ng-diagram-background />
</ng-diagram>
</div>
`,
styleUrls: ['./diagram.component.scss'],
})
export class DiagramComponent {
private ngDiagramService = inject(NgDiagramService);
private modelService = inject(NgDiagramModelService);
config: NgDiagramConfig = {
debugMode: true,
};
model = initializeModel({
nodes: [
{
id: '1',
position: { x: 200, y: 200 },
data: { label: 'Use Buttons to test transaction' },
},
],
});
onTestTransactionClick() {
this.cleanDiagram();
this.ngDiagramService.transaction(() => {
this.createDiagram('Transaction Node');
});
}
onTestWithoutTransactionClick() {
this.cleanDiagram();
this.createDiagram('Non-transaction Node');
}
private createDiagram(nodeName: string) {
this.modelService.addNodes([
{
id: '1',
position: { x: 100, y: 100 },
data: { label: nodeName },
},
{
id: '2',
position: { x: 100, y: 200 },
data: { label: nodeName },
},
{
id: '3',
position: { x: 100, y: 300 },
data: { label: nodeName },
},
]);
this.modelService.updateNodeData('1', {
label: `Updated ${nodeName} 1`,
});
this.modelService.updateNodeData('2', {
label: `Updated ${nodeName} 2`,
});
this.modelService.updateNodeData('3', {
label: `Updated ${nodeName} 3`,
});
this.modelService.addEdges([
{
id: 'edge-1',
source: '1',
target: '2',
sourcePort: 'port-right',
targetPort: 'port-left',
data: {},
},
{
id: 'edge-2',
source: '2',
target: '3',
sourcePort: 'port-right',
targetPort: 'port-left',
data: {},
},
]);
}
private cleanDiagram() {
this.modelService.deleteNodes(
this.modelService.nodes().map((node) => node.id)
);
}
}

Whether you’re building simple diagrams or complex applications, transactions help ensure data consistency and optimal performance.