Skip to main content

Storage Services

Tess UI Library fornisce servizi di storage astratti per gestire dati locali, di sessione e persistenti attraverso LocalStorage, SessionStorage e IndexedDB.

Panoramica

I servizi di storage forniscono un'interfaccia unificata e type-safe per la persistenza dei dati nel browser, con supporto per:

  • Type-safety: Generics TypeScript per dati tipizzati
  • Result Pattern: Gestione errori consistente
  • Serializzazione automatica: JSON serialization/deserialization
  • Namespace: Prefissi per evitare conflitti
  • Expirazione: TTL opzionale per i dati
  • Observable: Reactive updates con RxJS

Servizi Disponibili

TessStorageService

Interfaccia base per tutti i servizi di storage.

interface TessStorageService {
set<T>(key: string, value: T, ttl?: number): Result<void>;
get<T>(key: string): Result<T | null>;
remove(key: string): Result<void>;
clear(): Result<void>;
has(key: string): boolean;
keys(): string[];
}

TessLocalStorageService

Servizio per gestire il localStorage del browser (persistente tra sessioni).

import { Component, inject } from '@angular/core';
import { TessLocalStorageService } from '@tess-ui-library/core';

@Component({
selector: 'app-settings',
standalone: true,
template: `...`,
})
export class SettingsComponent {
private localStorage = inject(TessLocalStorageService);

saveSettings(settings: AppSettings) {
const result = this.localStorage.set('app-settings', settings);

if (result.isSuccess) {
console.log('Impostazioni salvate');
} else {
console.error('Errore salvataggio:', result.errorMessage);
}
}

loadSettings(): AppSettings | null {
const result = this.localStorage.get<AppSettings>('app-settings');
return result.isSuccess ? result.value : null;
}
}

TessSessionStorageService

Servizio per gestire il sessionStorage del browser (dura solo per la sessione corrente).

import { Component, inject } from '@angular/core';
import { TessSessionStorageService } from '@tess-ui-library/core';

@Component({
selector: 'app-wizard',
standalone: true,
template: `...`,
})
export class WizardComponent {
private sessionStorage = inject(TessSessionStorageService);

saveWizardStep(step: number, data: any) {
this.sessionStorage.set(`wizard-step-${step}`, data);
}

loadWizardStep(step: number): any {
const result = this.sessionStorage.get(`wizard-step-${step}`);
return result.value;
}

clearWizard() {
// Rimuove tutti i dati del wizard
const keys = this.sessionStorage.keys().filter(k => k.startsWith('wizard-step-'));
keys.forEach(key => this.sessionStorage.remove(key));
}
}

TessIndexedDBService

Servizio per gestire IndexedDB (database locale asincrono, per grandi quantità di dati).

import { Component, inject } from '@angular/core';
import { TessIndexedDBService } from '@tess-ui-library/core';

@Component({
selector: 'app-offline-data',
standalone: true,
template: `...`,
})
export class OfflineDataComponent {
private indexedDB = inject(TessIndexedDBService);

async saveData(data: LargeDataSet) {
const result = await this.indexedDB.set('large-dataset', data);

if (result.isSuccess) {
console.log('Dati salvati in IndexedDB');
}
}

async loadData(): Promise<LargeDataSet | null> {
const result = await this.indexedDB.get<LargeDataSet>('large-dataset');
return result.value;
}
}

API Dettagliata

set<T>(key: string, value: T, ttl?: number): Result<void>

Salva un valore nello storage.

Parametri:

  • key: Chiave univoca per il valore
  • value: Valore da salvare (sarà serializzato in JSON)
  • ttl: (Opzionale) Time-to-live in millisecondi
// Salvataggio semplice
this.localStorage.set('user', { id: 1, name: 'John' });

// Con TTL (scade dopo 1 ora)
this.localStorage.set('temp-token', 'abc123', 60 * 60 * 1000);

get<T>(key: string): Result<T | null>

Recupera un valore dallo storage.

const result = this.localStorage.get<User>('user');

if (result.isSuccess && result.value) {
console.log('User:', result.value);
} else if (result.isSuccess && result.value === null) {
console.log('Chiave non trovata');
} else {
console.error('Errore:', result.errorMessage);
}

remove(key: string): Result<void>

Rimuove un valore dallo storage.

const result = this.localStorage.remove('user');

if (result.isSuccess) {
console.log('User rimosso');
}

clear(): Result<void>

Rimuove tutti i valori dallo storage (con namespace se configurato).

this.localStorage.clear();

has(key: string): boolean

Verifica se una chiave esiste nello storage.

if (this.localStorage.has('user')) {
console.log('User presente');
}

keys(): string[]

Ritorna tutte le chiavi presenti nello storage.

const allKeys = this.localStorage.keys();
console.log('Chiavi:', allKeys);

Configurazione

Namespace

Configura un namespace globale per evitare conflitti con altre applicazioni:

// app.config.ts
import { STORAGE_CONFIG } from '@tess-ui-library/core';

export const appConfig: ApplicationConfig = {
providers: [
{
provide: STORAGE_CONFIG,
useValue: {
namespace: 'myapp', // Tutte le chiavi avranno prefisso 'myapp:'
},
},
],
};

Esempi Pratici

Cache di Dati API

@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
private localStorage = inject(TessLocalStorageService);
private cacheKey = 'users-cache';
private cacheTTL = 5 * 60 * 1000; // 5 minuti

getUsers(): Observable<User[]> {
// Prova a leggere dalla cache
const cached = this.localStorage.get<User[]>(this.cacheKey);

if (cached.isSuccess && cached.value) {
return of(cached.value);
}

// Fetch da API e salva in cache
return this.http.get<User[]>('/api/users').pipe(
tap(users => {
this.localStorage.set(this.cacheKey, users, this.cacheTTL);
})
);
}

invalidateCache() {
this.localStorage.remove(this.cacheKey);
}
}

Wizard Multi-Step

@Component({
selector: 'app-registration-wizard',
standalone: true,
template: `...`,
})
export class RegistrationWizardComponent implements OnDestroy {
private sessionStorage = inject(TessSessionStorageService);
currentStep = 1;

saveStep(step: number, data: any) {
this.sessionStorage.set(\`registration-step-\${step}\`, data);
}

loadStep(step: number): any {
const result = this.sessionStorage.get(\`registration-step-\${step}\`);
return result.value || {};
}

complete() {
// Combina tutti gli step
const registration = {
...this.loadStep(1),
...this.loadStep(2),
...this.loadStep(3),
};

this.apiService.register(registration).subscribe({
next: () => {
// Pulisci dopo registrazione riuscita
this.clearWizardData();
}
});
}

ngOnDestroy() {
// Pulisci in caso di navigazione via
this.clearWizardData();
}

private clearWizardData() {
[1, 2, 3].forEach(step => {
this.sessionStorage.remove(\`registration-step-\${step}\`);
});
}
}

Preferenze Utente

interface UserPreferences {
theme: 'light' | 'dark';
language: string;
notifications: boolean;
pageSize: number;
}

@Injectable({ providedIn: 'root' })
export class PreferencesService {
private localStorage = inject(TessLocalStorageService);
private preferencesKey = 'user-preferences';

private defaultPreferences: UserPreferences = {
theme: 'light',
language: 'it',
notifications: true,
pageSize: 10,
};

getPreferences(): UserPreferences {
const result = this.localStorage.get<UserPreferences>(this.preferencesKey);
return result.value || this.defaultPreferences;
}

updatePreferences(preferences: Partial<UserPreferences>) {
const current = this.getPreferences();
const updated = { ...current, ...preferences };
this.localStorage.set(this.preferencesKey, updated);
}

resetPreferences() {
this.localStorage.set(this.preferencesKey, this.defaultPreferences);
}
}

Offline Data Sync

@Injectable({ providedIn: 'root' })
export class OfflineSyncService {
private indexedDB = inject(TessIndexedDBService);
private http = inject(HttpClient);

async saveOffline(entity: Entity) {
const pending = await this.getPendingSync();
pending.push(entity);
await this.indexedDB.set('pending-sync', pending);
}

async syncWithServer() {
const pending = await this.getPendingSync();

for (const entity of pending) {
try {
await firstValueFrom(this.http.post('/api/entities', entity));
// Rimuovi da pending dopo sync riuscito
pending.splice(pending.indexOf(entity), 1);
} catch (error) {
console.error('Sync failed for entity:', entity, error);
}
}

await this.indexedDB.set('pending-sync', pending);
}

private async getPendingSync(): Promise<Entity[]> {
const result = await this.indexedDB.get<Entity[]>('pending-sync');
return result.value || [];
}
}

Token Authentication

@Injectable({ providedIn: 'root' })
export class AuthTokenService {
private localStorage = inject(TessLocalStorageService);
private sessionStorage = inject(TessSessionStorageService);

saveToken(token: string, rememberMe: boolean) {
const storage = rememberMe ? this.localStorage : this.sessionStorage;
storage.set('auth-token', token);
}

getToken(): string | null {
// Prova prima session, poi local
let result = this.sessionStorage.get<string>('auth-token');

if (!result.value) {
result = this.localStorage.get<string>('auth-token');
}

return result.value;
}

clearToken() {
this.localStorage.remove('auth-token');
this.sessionStorage.remove('auth-token');
}

hasToken(): boolean {
return this.localStorage.has('auth-token') ||
this.sessionStorage.has('auth-token');
}
}

Best Practices

  1. Usa il storage appropriato:

    • LocalStorage: Dati persistenti (preferenze, auth token con "remember me")
    • SessionStorage: Dati temporanei (wizard steps, form drafts)
    • IndexedDB: Grandi quantità di dati o dati offline
  2. Gestisci sempre i Result: Controlla sempre isSuccess prima di usare value

  3. Usa TTL per dati temporanei: Imposta TTL per cache e dati che scadono

  4. Namespace per multi-tenancy: Usa namespace per applicazioni multi-tenant

  5. Type-safety: Definisci interfacce TypeScript per i tuoi dati

  6. Cleanup: Rimuovi dati non più necessari per non saturare lo storage

  7. Non salvare dati sensibili: Non salvare password o dati sensibili in plain text

  8. Size limits:

    • LocalStorage/SessionStorage: ~5-10 MB
    • IndexedDB: Dipende dal browser (generalmente molto più grande)

Vedi Anche