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]);
});

Async Transactions since v0.9.0

Section titled “Async Transactions ”

Transactions support async callbacks, allowing you to perform asynchronous operations like fetching data from a server before modifying the diagram state.

await this.ngDiagramService.transaction(async () => {
// Fetch data from server
const nodes = await this.nodeService.fetchNodes();
// Add nodes after data is fetched
this.ngDiagramModelService.addNodes(nodes);
});
console.log('Transaction complete - all nodes added');

The transaction promise resolves after all operations inside the callback are complete and the state has been updated.

Transaction Options since v0.9.0

Section titled “Transaction Options ”

Transactions accept an optional second parameter for additional configuration.

When adding nodes or edges, the diagram needs to measure their dimensions before they’re fully rendered. By default, the transaction resolves immediately after the state update, before measurements complete.

Use waitForMeasurements: true when you need to perform operations that depend on measured values, such as zooming to fit new nodes:

// Add nodes and wait for their dimensions to be measured
await this.ngDiagramService.transaction(
() => {
this.ngDiagramModelService.addNodes([newNode]);
},
{ waitForMeasurements: true }
);
// Now safe to zoom - node dimensions are known
this.ngDiagramViewportService.zoomToFit();

This works with both sync and async transactions:

// Async transaction with measurements
await this.ngDiagramService.transaction(
async () => {
const data = await fetchNodeDataFromServer();
const newNode: Node = {
id: 'fetched-node',
type: 'default',
position: { x: 1000, y: 1000 },
data,
};
this.ngDiagramModelService.addNodes([newNode]);
},
{ waitForMeasurements: true }
);
// Zoom to include the new node with correct dimensions
this.ngDiagramViewportService.zoomToFit();

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
  • Operations requiring measurements - When you need to zoom to fit or center on newly added elements

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.