Skip to content

Save Persistence

This example demonstrates how to implement save to the diagram in ngDiagram using Angular features.

The save functionality allows users to persist the current diagram (nodes and edges) in local storage, restore it later, or clear the saved state.

  • 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.

The SaveStateService manages saving, loading, and clearing the diagram state using Angular signals and effects:

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

The navigation bar provides buttons for saving, loading, and clearing the diagram state. Button states are reactive:

4 collapsed lines
import { Component, computed, inject, output, signal } from '@angular/core';
import { NgDiagramModelService } 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>
`,
styleUrls: ['./nav-bar.component.scss'],
})
export class NavBarComponent {
42 collapsed lines
//TODO - change any type to the correct one
loadModel = output<any>();
private readonly saveStateService = inject(SaveStateService);
private readonly modelService = inject(NgDiagramModelService);
isSaved = computed(() => {
return this.saveStateService.state() !== null;
});
private statusMessage = signal('');
private statusTimeout: ReturnType<typeof setTimeout> | null = null;
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);

The example wraps the diagram and navigation bar in a context provider, ensuring services are available.

  • Save: Serializes and stores the current diagram state.
  • Load: Restores the diagram from the saved state.
  • Clear: Removes the saved state from local
import { Component, computed, inject, output, signal } from '@angular/core';
import { NgDiagramModelService } 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>
`,
styleUrls: ['./nav-bar.component.scss'],
})
export class NavBarComponent {
//TODO - change any type to the correct one
loadModel = output<any>();
private readonly saveStateService = inject(SaveStateService);
private readonly modelService = inject(NgDiagramModelService);
isSaved = computed(() => {
return this.saveStateService.state() !== null;
});
private statusMessage = signal('');
private statusTimeout: ReturnType<typeof setTimeout> | null = null;
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);
}
}