State Management
ngDiagram uses a reactive state management system that allows you to create, access, and modify diagram data efficiently. Understanding this system is essential for building dynamic, interactive diagrams.
Creating a Model
Section titled “Creating a Model”The model is the core data structure that holds all your diagram information including nodes, edges, and metadata. Use the initializeModel
function to create a new model with initial data.
Basic Model Creation
Section titled “Basic Model Creation”import { initializeModel } from 'ng-diagram';
const model = initializeModel({ nodes: [ { id: '1', position: { x: 100, y: 150 }, data: { label: 'Node 1' }, }, { id: '2', position: { x: 400, y: 150 }, data: { label: 'Node 2' }, }, ], edges: [ { id: 'edge-1', source: '1', target: '2', data: {}, }, ],});
Model with Metadata
Section titled “Model with Metadata”You can also initialize your model with metadata such as viewport settings:
const model = initializeModel({ metadata: { viewport: { x: 100, y: 50, scale: 0.8 }, }, nodes: [ // ... your nodes ], edges: [ // ... your edges ],});
Despite preconfigured metadata, you can keep there any additional custom properties that you may need in your application.
TypeScript Support
Section titled “TypeScript Support”For better type safety with custom data types, you can specify them when creating your model:
interface CustomNodeData { label: string; category: 'input' | 'output' | 'process';}
interface CustomEdgeData { label?: string; style?: 'solid' | 'dashed';}
const model = initializeModel({ nodes: [ { id: '1', position: { x: 100, y: 150 }, data: { label: 'Process Node', category: 'process' } as CustomNodeData, }, // ... your nodes ], edges: [ { id: 'edge-1', source: '1', target: '2', data: { label: 'Connection', style: 'solid' } as CustomEdgeData, }, // ... your edges ],});
Accessing the Model Service
Section titled “Accessing the Model Service”The NgDiagramModelService
provides a reactive interface to interact with your diagram’s state. It becomes available once you register provideNgDiagram()
and can then be injected into your components.
Injecting the Service
Section titled “Injecting the Service”import { Component, inject } from '@angular/core';import { NgDiagramModelService } from 'ng-diagram';
@Component({ // ...})export class MyComponent { private modelService = inject(NgDiagramModelService);}
Accessing Reactive Data
Section titled “Accessing Reactive Data”The model service provides reactive signals for nodes, edges, and metadata:
export class MyComponent { private modelService = inject(NgDiagramModelService);
// Reactive signals nodes = this.modelService.nodes; edges = this.modelService.edges; metadata = this.modelService.metadata;
ngOnInit() { // Access current values console.log('Current nodes:', this.nodes()); console.log('Current edges:', this.edges()); console.log('Current viewport:', this.metadata().viewport); }}
Using in Templates
Section titled “Using in Templates”You can bind directly to the reactive signals in your templates:
<div> <p>Total nodes: {{ nodes().length }}</p> <p>Total edges: {{ edges().length }}</p> <p>Current scale: {{ metadata().viewport.scale }}</p></div>
Performing Operations
Section titled “Performing Operations”The model service provides comprehensive methods for adding, updating, deleting, and querying diagram elements.
Adding Elements
Section titled “Adding Elements”Add new nodes and edges to your diagram:
// Add new nodesthis.modelService.addNodes([ { id: 'new-node', position: { x: 200, y: 200 }, data: { label: 'New Node' }, },]);
// Add new edgesthis.modelService.addEdges([ { id: 'new-edge', source: '1', target: 'new-node', sourcePort: 'port-right', targetPort: 'port-left', data: {}, },]);
Updating Elements
Section titled “Updating Elements”Update properties of existing nodes and edges:
// Update entire nodethis.modelService.updateNode('node-1', { position: { x: 300, y: 300 }, data: { label: 'Updated Node' },});
// Update only node datathis.modelService.updateNodeData('node-1', { label: 'New Label', color: 'blue',});
// Update multiple nodes at oncethis.modelService.updateNodes([ { id: '1', position: { x: 100, y: 100 } }, { id: '2', position: { x: 200, y: 200 } },]);
// Update edge propertiesthis.modelService.updateEdge('edge-1', { data: { label: 'Updated Edge' },});
// Update only edge datathis.modelService.updateEdgeData('edge-1', { label: 'Connection', style: 'dashed',});
Deleting Elements
Section titled “Deleting Elements”Remove nodes and edges from your diagram:
// Delete nodesthis.modelService.deleteNodes(['node-1', 'node-2']);
// Delete edgesthis.modelService.deleteEdges(['edge-1', 'edge-2']);
Querying Elements
Section titled “Querying Elements”Find and retrieve specific elements:
// Get element by IDconst node = this.modelService.getNodeById('node-1');const edge = this.modelService.getEdgeById('edge-1');
// Find nearest node to a pointconst nearestNode = this.modelService.getNearestNodeInRange( { x: 150, y: 150 }, 50 // range in pixels);
// Find nearest port to a pointconst nearestPort = this.modelService.getNearestPortInRange({ x: 150, y: 150 }, 30);
// Get all nodes in a rangeconst nodesInRange = this.modelService.getNodesInRange({ x: 150, y: 150 }, 100);
Model Serialization
Section titled “Model Serialization”Save and restore your diagram state:
// Export model to JSONconst jsonString = this.modelService.toJSON();
// Store in localStoragelocalStorage.setItem('diagram-state', jsonString);
// Load from localStorageconst savedState = localStorage.getItem('diagram-state');if (savedState) { const parsedModel = JSON.parse(savedState);
// Create new model from saved state this.model = initializeModel(parsedModel);}
Best Practices
Section titled “Best Practices”Reactive Updates
Section titled “Reactive Updates”Always use the model service methods rather than directly modifying the model:
// ❌ Don't modify directlythis.model.nodes[0].position = { x: 100, y: 100 };
// ❌ Also don't use model methods directlythis.model.updateNode('node-id', { position: { x: 100, y: 100 } });
// ✅ Use model service methodsthis.modelService.updateNode('node-id', { position: { x: 100, y: 100 },});
TypeScript Types
Section titled “TypeScript Types”Leverage TypeScript for better development experience:
interface NodeData { label: string; color?: string; category: 'input' | 'output' | 'process';}
interface EdgeData { label?: string; style?: 'solid' | 'dashed';}
// Type your updatesthis.modelService.updateNodeData<NodeData>('node-1', { label: 'Process Node', category: 'process',});
this.modelService.updateEdgeData<EdgeData>('edge-1', { style: 'dashed',});
Performance Considerations
Section titled “Performance Considerations”For bulk operations, use batch methods when available:
// ✅ Better performance for multiple updatesthis.modelService.updateNodes([ { id: '1', position: { x: 100, y: 100 } }, { id: '2', position: { x: 200, y: 200 } }, { id: '3', position: { x: 300, y: 300 } },]);
// ❌ Less efficient for multiple updatesthis.modelService.updateNode('1', { position: { x: 100, y: 100 } });this.modelService.updateNode('2', { position: { x: 200, y: 200 } });this.modelService.updateNode('3', { position: { x: 300, y: 300 } });
The state management system provides a robust foundation for building dynamic diagrams that can respond to user interactions and external data changes while maintaining excellent performance and type safety.
Creating Custom Model Implementations
Section titled “Creating Custom Model Implementations”The NgDiagramComponent
accepts any object that implements the ModelAdapter
interface, which means you can create your own custom model implementations beyond the default SignalModelAdapter
provided by initializeModel
. This allows for advanced use cases like connecting to external data sources, implementing custom persistence layers, or integrating with existing state management solutions.
Understanding the ModelAdapter Interface
Section titled “Understanding the ModelAdapter Interface”The ModelAdapter
interface defines the contract that any model implementation must fulfill. You can find the complete interface documentation in the API reference. The key methods include data access (getNodes
, getEdges
, getMetadata
), data modification (updateNodes
, updateEdges
, updateMetadata
), change notification system (onChange
, unregisterOnChange
), and lifecycle management (destroy
, undo
, redo
, toJSON
).
For a complete example of implementing a custom model adapter, see the Custom Model example.
Advanced Use Cases
Section titled “Advanced Use Cases”Custom model implementations enable several advanced scenarios:
Real-time Collaboration
Section titled “Real-time Collaboration”Multi-user collaboration requires synchronizing diagram changes across different clients in real-time. A collaborative model adapter would typically integrate with WebSocket connections, services like Socket.IO or libraries like Yjs to broadcast changes and apply remote updates while handling conflict resolution.
State Management Integration
Section titled “State Management Integration”When working with complex Angular applications that use state management libraries like NgRx or Akita, you can create model adapters that integrate seamlessly with your existing store architecture. These adapters would dispatch actions for diagram changes and subscribe to state selections, ensuring that diagram data follows your application’s established data flow patterns and benefits from features like time-travel debugging and state persistence.
class NgRxModelAdapter implements ModelAdapter { constructor(private store: Store) {}
updateNodes(nodes: Node[]): void { this.store.dispatch(DiagramActions.updateNodes({ nodes })); }
onChange(callback: Function): void { this.store.select(selectDiagramData).subscribe(callback); }}
Custom model implementations provide the flexibility to integrate NgDiagram with any data architecture while maintaining full compatibility with all diagram features and the model service API.