| name | @delon/cache Caching Strategies |
| description | Implement caching strategies using @delon/cache. Use this skill when adding memory cache, LocalStorage cache, SessionStorage cache, or cache interceptors for HTTP requests. Supports TTL-based expiration, cache invalidation, cache grouping, and persistent storage. Optimizes performance by reducing redundant API calls and database queries. |
| license | MIT |
@delon/cache Caching Strategies Skill
This skill helps implement caching using @delon/cache library.
Core Principles
Cache Types
- Memory Cache: Fast, in-memory caching (lost on page refresh)
- LocalStorage Cache: Persistent across sessions
- SessionStorage Cache: Persistent within session only
Features
- TTL-based expiration
- Cache invalidation (manual and automatic)
- Cache grouping and namespacing
- HTTP request caching with interceptors
- Observable support for async data
Configuration
import { ApplicationConfig } from '@angular/core';
import { provideDelonCache } from '@delon/cache';
export const appConfig: ApplicationConfig = {
providers: [
provideDelonCache({
mode: 'promise',
request_method: 'POST',
meta_key: '__cache_meta',
prefix: '',
expire: 3600000
})
]
};
Cache Service
import { Injectable, inject } from '@angular/core';
import { CacheService as DelonCacheService } from '@delon/cache';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class CacheService {
private cache = inject(DelonCacheService);
set<T>(key: string, value: T, options?: {
type?: 'memory' | 'localStorage' | 'sessionStorage';
expire?: number;
}): void {
this.cache.set(key, value, {
type: options?.type || 'memory',
expire: options?.expire || 3600000
});
}
get<T>(key: string): T | null {
return this.cache.get<T>(key);
}
has(key: string): boolean {
return this.cache.has(key);
}
remove(key: string): void {
this.cache.remove(key);
}
clear(): void {
this.cache.clear();
}
getOrSet<T>(
key: string,
factory: () => Observable<T> | Promise<T>,
options?: {
type?: 'memory' | 'localStorage' | 'sessionStorage';
expire?: number;
}
): Observable<T> {
if (this.has(key)) {
return new Observable(observer => {
observer.next(this.get<T>(key)!);
observer.complete();
});
}
const result = factory();
if (result instanceof Observable) {
return new Observable(observer => {
result.subscribe({
next: (value) => {
this.set(key, value, options);
observer.next(value);
},
error: (err) => observer.error(err),
complete: () => observer.complete()
});
});
}
return new Observable(observer => {
result.then(value => {
this.set(key, value, options);
observer.next(value);
observer.complete();
}).catch(err => observer.error(err));
});
}
}
Memory Cache (Default)
import { Component, inject, signal } from '@angular/core';
import { CacheService } from '@core/services/cache.service';
@Component({
selector: 'app-task-list',
template: `
<button nz-button (click)="loadTasks()">Load Tasks</button>
<button nz-button (click)="clearCache()">Clear Cache</button>
@if (loading()) {
<nz-spin />
} @else {
@for (task of tasks(); track task.id) {
<div>{{ task.title }}</div>
}
}
`
})
export class TaskListComponent {
private cacheService = inject(CacheService);
private taskService = inject(TaskService);
loading = signal(false);
tasks = signal<Task[]>([]);
private readonly CACHE_KEY = 'tasks:list';
async loadTasks(): Promise<void> {
const cached = this.cacheService.get<Task[]>(this.CACHE_KEY);
if (cached) {
console.log('Loading from cache');
this.tasks.set(cached);
return;
}
this.loading.set(true);
try {
const tasks = await this.taskService.getTasks();
this.cacheService.set(this.CACHE_KEY, tasks, {
type: 'memory',
expire: 5 * 60 * 1000
});
this.tasks.set(tasks);
} finally {
this.loading.set(false);
}
}
clearCache(): void {
this.cacheService.remove(this.CACHE_KEY);
console.log('Cache cleared');
}
}
LocalStorage Cache (Persistent)
import { Injectable, inject } from '@angular/core';
import { CacheService } from '@core/services/cache.service';
@Injectable({ providedIn: 'root' })
export class UserPreferencesService {
private cacheService = inject(CacheService);
private readonly CACHE_KEY = 'user:preferences';
savePreferences(preferences: UserPreferences): void {
this.cacheService.set(this.CACHE_KEY, preferences, {
type: 'localStorage',
expire: 30 * 24 * 60 * 60 * 1000
});
}
loadPreferences(): UserPreferences | null {
return this.cacheService.get<UserPreferences>(this.CACHE_KEY);
}
clearPreferences(): void {
this.cacheService.remove(this.CACHE_KEY);
}
}
interface UserPreferences {
theme: 'light' | 'dark';
language: string;
sidebarCollapsed: boolean;
}
SessionStorage Cache
@Injectable({ providedIn: 'root' })
export class SearchService {
private cacheService = inject(CacheService);
async search(query: string): Promise<SearchResult[]> {
const cacheKey = `search:${query}`;
const cached = this.cacheService.get<SearchResult[]>(cacheKey);
if (cached) {
return cached;
}
const results = await this.performSearch(query);
this.cacheService.set(cacheKey, results, {
type: 'sessionStorage',
expire: 30 * 60 * 1000
});
return results;
}
private async performSearch(query: string): Promise<SearchResult[]> {
return [];
}
}
Lazy Loading with getOrSet
@Injectable({ providedIn: 'root' })
export class ConfigService {
private cacheService = inject(CacheService);
private http = inject(HttpClient);
loadConfig(): Observable<AppConfig> {
return this.cacheService.getOrSet(
'app:config',
() => this.http.get<AppConfig>('/api/config'),
{
type: 'localStorage',
expire: 24 * 60 * 60 * 1000
}
);
}
}
Cache Invalidation
Manual Invalidation
@Injectable({ providedIn: 'root' })
export class TaskService {
private cacheService = inject(CacheService);
private taskRepository = inject(TaskRepository);
async createTask(task: Omit<Task, 'id'>): Promise<Task> {
const created = await this.taskRepository.create(task);
this.cacheService.remove('tasks:list');
this.cacheService.remove(`tasks:blueprint:${task.blueprintId}`);
return created;
}
async updateTask(id: string, updates: Partial<Task>): Promise<Task> {
const updated = await this.taskRepository.update(id, updates);
this.cacheService.remove(`tasks:${id}`);
this.cacheService.remove('tasks:list');
return updated;
}
}
Group Cache Invalidation
@Injectable({ providedIn: 'root' })
export class CacheInvalidationService {
private cacheService = inject(CacheService);
invalidateGroup(prefix: string): void {
const keys = this.getCacheKeys(prefix);
keys.forEach(key => this.cacheService.remove(key));
}
private cacheKeys = new Set<string>();
registerCacheKey(key: string): void {
this.cacheKeys.add(key);
}
private getCacheKeys(prefix: string): string[] {
return Array.from(this.cacheKeys).filter(key => key.startsWith(prefix));
}
}
HTTP Cache Interceptor
import { HttpInterceptorFn, HttpResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { CacheService } from '@core/services/cache.service';
import { of, tap } from 'rxjs';
export const cacheInterceptor: HttpInterceptorFn = (req, next) => {
const cacheService = inject(CacheService);
if (req.method !== 'GET') {
return next(req);
}
const skipCache = req.headers.has('X-Skip-Cache') ||
req.url.includes('/api/realtime');
if (skipCache) {
return next(req);
}
const cacheKey = `http:${req.urlWithParams}`;
const cached = cacheService.get<HttpResponse<any>>(cacheKey);
if (cached) {
console.log('Serving from cache:', cacheKey);
return of(cached);
}
return next(req).pipe(
tap(event => {
if (event instanceof HttpResponse) {
cacheService.set(cacheKey, event, {
type: 'memory',
expire: 5 * 60 * 1000
});
}
})
);
};
Register Interceptor
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { cacheInterceptor } from '@core/interceptors/cache.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([cacheInterceptor])
)
]
};
Cache Namespacing
@Injectable({ providedIn: 'root' })
export class NamespacedCacheService {
private cacheService = inject(CacheService);
constructor(private namespace: string) {}
private getKey(key: string): string {
return `${this.namespace}:${key}`;
}
set<T>(key: string, value: T, options?: any): void {
this.cacheService.set(this.getKey(key), value, options);
}
get<T>(key: string): T | null {
return this.cacheService.get<T>(this.getKey(key));
}
remove(key: string): void {
this.cacheService.remove(this.getKey(key));
}
}
@Injectable({ providedIn: 'root' })
export class TaskCacheService extends NamespacedCacheService {
constructor() {
super('tasks');
}
}
Cache Patterns
Read-Through Cache
async getTask(id: string): Promise<Task> {
const cacheKey = `tasks:${id}`;
let task = this.cacheService.get<Task>(cacheKey);
if (task) {
return task;
}
task = await this.taskRepository.findById(id);
if (task) {
this.cacheService.set(cacheKey, task, {
type: 'memory',
expire: 10 * 60 * 1000
});
}
return task;
}
Write-Through Cache
async updateTask(id: string, updates: Partial<Task>): Promise<Task> {
const updated = await this.taskRepository.update(id, updates);
const cacheKey = `tasks:${id}`;
this.cacheService.set(cacheKey, updated, {
type: 'memory',
expire: 10 * 60 * 1000
});
return updated;
}
Cache-Aside Pattern
async getTasks(blueprintId: string): Promise<Task[]> {
const cacheKey = `tasks:blueprint:${blueprintId}`;
if (this.cacheService.has(cacheKey)) {
return this.cacheService.get<Task[]>(cacheKey)!;
}
const tasks = await this.taskRepository.findByBlueprintId(blueprintId);
this.cacheService.set(cacheKey, tasks, {
type: 'memory',
expire: 5 * 60 * 1000
});
return tasks;
}
Best Practices
Cache Key Naming
'users:123'
'tasks:blueprint:abc-123'
'config:app:theme'
'user'
'data123'
'cache'
TTL Guidelines
this.cacheService.set('config', data, { expire: 24 * 60 * 60 * 1000 });
this.cacheService.set('tasks', data, { expire: 5 * 60 * 1000 });
this.cacheService.set('user:prefs', data, { type: 'sessionStorage' });
this.cacheService.set('settings', data, {
type: 'localStorage',
expire: 30 * 24 * 60 * 60 * 1000
});
Checklist
When implementing caching:
References