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 valorevalue: 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
-
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
-
Gestisci sempre i Result: Controlla sempre
isSuccessprima di usarevalue -
Usa TTL per dati temporanei: Imposta TTL per cache e dati che scadono
-
Namespace per multi-tenancy: Usa namespace per applicazioni multi-tenant
-
Type-safety: Definisci interfacce TypeScript per i tuoi dati
-
Cleanup: Rimuovi dati non più necessari per non saturare lo storage
-
Non salvare dati sensibili: Non salvare password o dati sensibili in plain text
-
Size limits:
- LocalStorage/SessionStorage: ~5-10 MB
- IndexedDB: Dipende dal browser (generalmente molto più grande)