Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/aiskillstore-delon-cache-caching-strategies/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 309% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 81% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 214% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 250% | 0% |
| case-19 | ✓→✗ | ▼ Worse | 110% | 0% |
This skill helps implement caching using @delon/cache library.
typescript// src/app/app.config.ts import { ApplicationConfig } from '@angular/core'; import { provideDelonCache } from '@delon/cache'; export const appConfig: ApplicationConfig = { providers: [ provideDelonCache({ mode: 'promise', // 'promise' | 'none' request_method: 'POST', meta_key: '__cache_meta', prefix: '', expire: 3600000 // Default TTL: 1 hour (ms) }) ] };
typescript// src/app/core/services/cache.service.ts 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 cache with key */ set<T>(key: string, value: T, options?: { type?: 'memory' | 'localStorage' | 'sessionStorage'; expire?: number; // TTL in milliseconds }): void { this.cache.set(key, value, { type: options?.type || 'memory', expire: options?.expire || 3600000 // 1 hour default }); } /** * Get cache by key */ get<T>(key: string): T | null { return this.cache.get<T>(key); } /** * Check if cache exists and is not expired */ has(key: string): boolean { return this.cache.has(key); } /** * Remove cache by key */ remove(key: string): void { this.cache.remove(key); } /** * Clear all cache */ clear(): void { this.cache.clear(); } /** * Get or set cache (lazy loading pattern) */ 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)); }); } }
typescriptimport { 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> { // Try to get from cache first const cached = this.cacheService.get<Task[]>(this.CACHE_KEY); if (cached) { console.log('Loading from cache'); this.tasks.set(cached); return; } // Load from API this.loading.set(true); try { const tasks = await this.taskService.getTasks(); // Cache for 5 minutes this.cacheService.set(this.CACHE_KEY, tasks, { type: 'memory', expire: 5 * 60 * 1000 // 5 minutes }); this.tasks.set(tasks); } finally { this.loading.set(false); } } clearCache(): void { this.cacheService.remove(this.CACHE_KEY); console.log('Cache cleared'); } }
typescriptimport { 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'; /** * Save user preferences (persists across sessions) */ savePreferences(preferences: UserPreferences): void { this.cacheService.set(this.CACHE_KEY, preferences, { type: 'localStorage', expire: 30 * 24 * 60 * 60 * 1000 // 30 days }); } /** * Load user preferences */ loadPreferences(): UserPreferences | null { return this.cacheService.get<UserPreferences>(this.CACHE_KEY); } /** * Clear preferences */ clearPreferences(): void { this.cacheService.remove(this.CACHE_KEY); } } interface UserPreferences { theme: 'light' | 'dark'; language: string; sidebarCollapsed: boolean; }
typescript/** * Cache search results for current session only */ @Injectable({ providedIn: 'root' }) export class SearchService { private cacheService = inject(CacheService); async search(query: string): Promise<SearchResult[]> { const cacheKey = `search:${query}`; // Check session cache const cached = this.cacheService.get<SearchResult[]>(cacheKey); if (cached) { return cached; } // Perform search const results = await this.performSearch(query); // Cache for current session only this.cacheService.set(cacheKey, results, { type: 'sessionStorage', expire: 30 * 60 * 1000 // 30 minutes }); return results; } private async performSearch(query: string): Promise<SearchResult[]> { // API call return []; } }
typescript@Injectable({ providedIn: 'root' }) export class ConfigService { private cacheService = inject(CacheService); private http = inject(HttpClient); /** * Load configuration with automatic caching */ loadConfig(): Observable<AppConfig> { return this.cacheService.getOrSet( 'app:config', () => this.http.get<AppConfig>('/api/config'), { type: 'localStorage', expire: 24 * 60 * 60 * 1000 // 24 hours } ); } }
typescript@Injectable({ providedIn: 'root' }) export class TaskService { private cacheService = inject(CacheService); private taskRepository = inject(TaskRepository); /** * Create task and invalidate cache */ async createTask(task: Omit<Task, 'id'>): Promise<Task> { const created = await this.taskRepository.create(task); // Invalidate task list cache this.cacheService.remove('tasks:list'); this.cacheService.remove(`tasks:blueprint:${task.blueprintId}`); return created; } /** * Update task and invalidate cache */ async updateTask(id: string, updates: Partial<Task>): Promise<Task> { const updated = await this.taskRepository.update(id, updates); // Invalidate specific task cache this.cacheService.remove(`tasks:${id}`); // Invalidate list caches this.cacheService.remove('tasks:list'); return updated; } }
typescript@Injectable({ providedIn: 'root' }) export class CacheInvalidationService { private cacheService = inject(CacheService); /** * Invalidate all caches with prefix */ invalidateGroup(prefix: string): void { // @delon/cache doesn't have built-in group invalidation // So we track cache keys manually 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)); } }
typescript// src/app/core/interceptors/cache.interceptor.ts import { HttpInterceptorFn, HttpResponse } from '@angular/common/http'; import { inject } from '@angular/core'; import { CacheService } from '@core/services/cache.service'; import { of, tap } from 'rxjs'; /** * Cache GET requests */ export const cacheInterceptor: HttpInterceptorFn = (req, next) => { const cacheService = inject(CacheService); // Only cache GET requests if (req.method !== 'GET') { return next(req); } // Skip cache for certain URLs const skipCache = req.headers.has('X-Skip-Cache') || req.url.includes('/api/realtime'); if (skipCache) { return next(req); } // Generate cache key from URL + params const cacheKey = `http:${req.urlWithParams}`; // Check cache const cached = cacheService.get<HttpResponse<any>>(cacheKey); if (cached) { console.log('Serving from cache:', cacheKey); return of(cached); } // Make request and cache response return next(req).pipe( tap(event => { if (event instanceof HttpResponse) { cacheService.set(cacheKey, event, { type: 'memory', expire: 5 * 60 * 1000 // 5 minutes }); } }) ); };
typescript// src/app/app.config.ts import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { cacheInterceptor } from '@core/interceptors/cache.interceptor'; export const appConfig: ApplicationConfig = { providers: [ provideHttpClient( withInterceptors([cacheInterceptor]) ) ] };
typescript@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)); } } // Usage @Injectable({ providedIn: 'root' }) export class TaskCacheService extends NamespacedCacheService { constructor() { super('tasks'); } }
typescriptasync getTask(id: string): Promise<Task> { const cacheKey = `tasks:${id}`; // Try cache let task = this.cacheService.get<Task>(cacheKey); if (task) { return task; } // Load from repository task = await this.taskRepository.findById(id); // Cache result if (task) { this.cacheService.set(cacheKey, task, { type: 'memory', expire: 10 * 60 * 1000 // 10 minutes }); } return task; }
typescriptasync updateTask(id: string, updates: Partial<Task>): Promise<Task> { // Update in repository const updated = await this.taskRepository.update(id, updates); // Update cache const cacheKey = `tasks:${id}`; this.cacheService.set(cacheKey, updated, { type: 'memory', expire: 10 * 60 * 1000 }); return updated; }
typescriptasync getTasks(blueprintId: string): Promise<Task[]> { const cacheKey = `tasks:blueprint:${blueprintId}`; // Check cache if (this.cacheService.has(cacheKey)) { return this.cacheService.get<Task[]>(cacheKey)!; } // Load from repository const tasks = await this.taskRepository.findByBlueprintId(blueprintId); // Populate cache this.cacheService.set(cacheKey, tasks, { type: 'memory', expire: 5 * 60 * 1000 }); return tasks; }
typescript// Good: Descriptive, hierarchical keys 'users:123' 'tasks:blueprint:abc-123' 'config:app:theme' // Bad: Generic, flat keys 'user' 'data123' 'cache'
typescript// Static data: 1 hour - 1 day this.cacheService.set('config', data, { expire: 24 * 60 * 60 * 1000 }); // Dynamic data: 1-10 minutes this.cacheService.set('tasks', data, { expire: 5 * 60 * 1000 }); // User-specific: Session duration this.cacheService.set('user:prefs', data, { type: 'sessionStorage' }); // Persistent: 30 days this.cacheService.set('settings', data, { type: 'localStorage', expire: 30 * 24 * 60 * 60 * 1000 });
When implementing caching:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 18,238 | 14,016 | -23% | 1 | 1 | 0% | 3,800 | 7,148 | +88% | 0 | 0 | — |
case-02 | fail→fail | 15,734 | 15,825 | +1% | 1 | 1 | 0% | 3,195 | 7,503 | +135% | 0 | 0 | — |
case-03 | pass→pass | 19,374 | 17,001 | -12% | 1 | 1 | 0% | 3,889 | 7,767 | +100% | 0 | 0 | — |
case-04 | fail→pass | 7,440 | 8,202 | +10% | 1 | 1 | 0% | 1,357 | 5,546 | +309% | 0 | 0 | — |
case-05 | fail→fail | 9,318 | 7,119 | -24% | 1 | 1 | 0% | 1,857 | 5,478 | +195% | 0 | 0 | — |
case-06 | fail→pass | 17,726 | 8,468 | -52% | 1 | 1 | 0% | 3,169 | 5,747 | +81% | 0 | 0 | — |
case-07 | fail→fail | 16,335 | 7,804 | -52% | 1 | 1 | 0% | 2,933 | 5,680 | +94% | 0 | 0 | — |
case-08 | pass→pass | 17,821 | 15,170 | -15% | 1 | 1 | 0% | 3,183 | 7,085 | +123% | 0 | 0 | — |
case-09 | pass→pass | 16,937 | 15,634 | -8% | 1 | 1 | 0% | 3,101 | 7,203 | +132% | 0 | 0 | — |
case-10 | pass→pass | 17,330 | 13,106 | -24% | 1 | 1 | 0% | 3,276 | 6,698 | +104% | 0 | 0 | — |
case-11 | fail→pass | 11,568 | 12,708 | +10% | 1 | 1 | 0% | 2,150 | 6,747 | +214% | 0 | 0 | — |
case-12 | pass→pass | 15,507 | 11,046 | -29% | 1 | 1 | 0% | 2,758 | 6,080 | +120% | 0 | 0 | — |
case-13 | fail→pass | 9,888 | 11,708 | +18% | 1 | 1 | 0% | 1,900 | 6,648 | +250% | 0 | 0 | — |
case-14 | pass→pass | 11,900 | 9,050 | -24% | 1 | 1 | 0% | 2,034 | 5,837 | +187% | 0 | 0 | — |
case-15 | pass→pass | 11,824 | 9,289 | -21% | 1 | 1 | 0% | 2,227 | 5,974 | +168% | 0 | 0 | — |
case-16 | pass→pass | 11,947 | 8,939 | -25% | 1 | 1 | 0% | 2,228 | 5,937 | +166% | 0 | 0 | — |
case-17 | pass→pass | 4,067 | 2,843 | -30% | 1 | 1 | 0% | 618 | 4,657 | +654% | 0 | 0 | — |
case-18 | pass→pass | 3,145 | 3,666 | +17% | 1 | 1 | 0% | 523 | 4,807 | +819% | 0 | 0 | — |
case-19 | pass→fail | 17,798 | 12,108 | -32% | 1 | 1 | 0% | 3,046 | 6,392 | +110% | 0 | 0 | — |
case-20 | pass→fail | 10,327 | 8,000 | -23% | 1 | 1 | 0% | 1,690 | 5,526 | +227% | 0 | 0 | — |
case-21 | pass→pass | 13,081 | 9,470 | -28% | 1 | 1 | 0% | 2,471 | 5,841 | +136% | 0 | 0 | — |
case-22 | pass→pass | 12,738 | 11,445 | -10% | 1 | 1 | 0% | 1,778 | 5,995 | +237% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of -14 percentage points is the difference between those two pass rates over the 22 comparable cases. 4 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.