Save Persistence Example
This example demonstrates how to implement save and restore functionality for the diagram in ngDiagram using Angular features.
import '@angular/compiler';import { Component, inject, Injector } from '@angular/core';import { initializeModel, NgDiagramBackgroundComponent, NgDiagramComponent, NgDiagramModelService, provideNgDiagram, type Model, type NgDiagramConfig,} from 'ng-diagram';import { NavBarComponent } from './nav-bar/nav-bar.component';import { SaveStateService } from './save.service';
@Component({ imports: [NgDiagramComponent, NgDiagramBackgroundComponent, NavBarComponent], template: ` <nav-bar (loadModel)="loadModel($event)"></nav-bar> <div class="not-content diagram"> <ng-diagram [model]="model" [config]="config"> <ng-diagram-background /> </ng-diagram> </div> `, styleUrl: './diagram.component.scss', providers: [NgDiagramModelService, SaveStateService, provideNgDiagram()],})export class DiagramComponent { private injector = inject(Injector); config = { zoom: { max: 3, zoomToFit: { onInit: true, padding: 155, }, }, } satisfies NgDiagramConfig;
model = initializeModel({ nodes: [ { id: '1', position: { x: 0, y: 0 }, data: {}, }, { id: '2', position: { x: 100, y: 50 }, data: {}, }, { id: '3', position: { x: 200, y: 100 }, data: {}, }, ], });
loadModel(model: Partial<Model>): void { this.model = initializeModel(model, this.injector); }}3 collapsed lines
import { Component, computed, inject, output, signal } from '@angular/core';import { NgDiagramModelService, type Model } from 'ng-diagram';import { SaveStateService } from '../save.service';
@Component({ selector: 'nav-bar', template: ` <div class="nav-bar"> <div class="status">{{ statusMessage() }}</div> <button (click)="save()">Save</button> <button (click)="load()" [disabled]="!isSaved()">Load</button> <button class="clear" (click)="clear()" [disabled]="!isSaved()"> Clear </button> </div> `, styleUrl: './nav-bar.component.scss',})export class NavBarComponent {44 collapsed lines
loadModel = output<Partial<Model>>();
private readonly saveStateService = inject(SaveStateService); private readonly modelService = inject(NgDiagramModelService); isSaved = computed(() => { return this.saveStateService.state() !== null; });
private statusTimeout: ReturnType<typeof setTimeout> | null = null; protected statusMessage = signal('');
save(): void { const model = this.modelService.toJSON(); this.saveStateService.save(model); this.showStatus('State has been successfully saved!'); }
load(): void { const model = this.saveStateService.load();
if (model) { this.loadModel.emit(model); this.showStatus('State has been loaded!'); } }
clear(): void { if (window.confirm('Are you sure you want to clear the saved state?')) { this.saveStateService.clear(); this.showStatus('State has been cleared!'); } }
showStatus(msg: string): void { this.statusMessage.set(msg);
if (this.statusTimeout) { clearTimeout(this.statusTimeout); } this.statusTimeout = setTimeout(() => { this.statusMessage.set(''); this.statusTimeout = null; }, 6000); }}8 collapsed lines
import { effect, Injectable, signal } from '@angular/core';import { type Edge, type Metadata, type Node } from 'ng-diagram';
@Injectable()export class SaveStateService { private KEY = 'ngDiagramSaveStateKey';
state = signal<string | null>(localStorage.getItem(this.KEY)); stateSync = effect(() => { if (this.state() === null) { localStorage.removeItem(this.KEY); } else { localStorage.setItem(this.KEY, this.state() as string); } });
save(newState: string): void { this.state.set(newState); }
load(): { nodes: Node[]; edges: Edge[]; metadata: Metadata } | null { try { const serializedState = this.state(); return serializedState ? JSON.parse(serializedState) : null; } catch (e) { console.error('SaveStateService: Error loading state', e); return null; } }
clear(): void { this.state.set(null); }}:host { position: relative; width: 100%; height: 100%; display: block; // needed for flex children to work, otherwise children renders outside of host}
.diagram { display: flex; height: var(--ng-diagram-height); border: var(--ng-diagram-border);}:host { position: absolute; top: 1rem; right: 1rem; z-index: 1; background-color: var(--ngd-node-bg-primary-default); border: var(--ng-diagram-border); padding: 1rem;}
.nav-bar { display: flex; gap: 8px; align-items: flex-end; justify-content: end;
& .status { flex: 1; padding: 2px 12px;
&:empty { background: none; padding: 0; } }
button { margin-top: 0; cursor: pointer;
&.clear { background: darkred; border-style: solid; border-color: darkred;
&:disabled { border-color: var(--ngd-node-border-color); } }
&:disabled { cursor: not-allowed;
&.clear { background: none; } } }}Additional Explanation
Section titled “Additional Explanation”Key Concepts
Section titled “Key Concepts”- Persistence: Saves the diagram state to local storage.
- Restoration: Loads the saved state back into the diagram.
- Clear: Removes the saved state from local storage.
Implementation Details
Section titled “Implementation Details”-
Save Persistence Service:
TheSaveStateServicemanages saving, loading, and clearing the diagram state using Angular signals and effects. -
NavBar Component:
The navigation bar provides buttons for saving, loading, and clearing the diagram state. Button states are reactive.
Actions
Section titled “Actions”- Save: Serializes and stores the current diagram state.
- Load: Restores the diagram from the saved state.
- Clear: Removes the saved state from local storage.
When to Use This Approach
Section titled “When to Use This Approach”This example keeps the default model and saves snapshots on demand with toJSON() — the simplest way to add save/load buttons to an app. If you want the model itself to live in your own storage layer (persist on every change, connect to a store or a backend), implement a custom ModelAdapter instead — see the Custom Model example.
Learn more: State Management guide →