Hone logo
Hone
Problems

Building a Persistent Cache Service in Angular

Caching is a crucial optimization technique for web applications, reducing server load and improving user experience by storing frequently accessed data locally. This challenge asks you to create a persistent cache service in Angular that utilizes the browser's localStorage to store cached data, ensuring data persists across browser sessions. This service should allow you to cache data, retrieve it, and invalidate specific cache entries.

Problem Description

You need to develop an Angular service called PersistentCacheService that provides the following functionalities:

  • set(key: string, data: any, expirySeconds?: number): Stores data in the cache using the provided key. Optionally, an expiry time in seconds can be specified. If an expiry time is provided, the cached data should be automatically removed after the specified duration.
  • get(key: string): Observable<any>: Retrieves data from the cache using the provided key. Returns an Observable that emits the cached data if found and hasn't expired. If the data is not found or has expired, the Observable should emit null.
  • delete(key: string): Removes a specific entry from the cache using the provided key.
  • clear(): Removes all entries from the cache.

The service should use localStorage for persistence. The expiry mechanism should use setTimeout to remove expired entries. The get method should return an Observable to handle asynchronous expiry checks and potential data retrieval failures gracefully.

Key Requirements:

  • The service must be injectable into Angular components.
  • Data should be serialized and deserialized correctly when storing and retrieving from localStorage. Use JSON.stringify and JSON.parse.
  • Expiry times should be handled accurately.
  • Error handling should be considered (e.g., localStorage unavailable).
  • The service should be thread-safe (consider potential race conditions if multiple components access the cache simultaneously). While perfect thread safety is difficult to guarantee in JavaScript, strive to minimize potential issues.

Expected Behavior:

  • Data stored using set should be available in subsequent browser sessions.
  • Data with an expiry time should be automatically removed after the specified duration.
  • get should return cached data immediately if available and not expired.
  • delete should remove the specified entry.
  • clear should remove all entries.
  • The service should handle cases where localStorage is unavailable (e.g., in private browsing mode).

Edge Cases to Consider:

  • localStorage is full. (While you don't need to implement complex eviction strategies, be aware of this limitation.)
  • Invalid keys (e.g., null, undefined, empty string).
  • Data that cannot be serialized/deserialized by JSON.
  • Expiry times that are zero or negative.

Examples

Example 1:

Input:
cacheService.set('user', { id: 1, name: 'John Doe' }, 60); // Cache user data for 60 seconds
cacheService.get('user').subscribe(userData => { console.log(userData); }); // Should log {id: 1, name: 'John Doe'} immediately
setTimeout(() => { cacheService.get('user').subscribe(userData => { console.log(userData); }); }, 61000); // After 61 seconds, should log null

Output:

{id: 1, name: 'John Doe'}
null

Explanation: The user data is cached and retrieved immediately. After 61 seconds (slightly more than the expiry time), the data is automatically removed, and get returns null.

Example 2:

Input:
cacheService.set('settings', { theme: 'dark' });
cacheService.get('settings').subscribe(settings => { console.log(settings); }); // Should log {theme: 'dark'}
cacheService.delete('settings');
cacheService.get('settings').subscribe(settings => { console.log(settings); }); // Should log null

Output:

{theme: 'dark'}
null

Explanation: The settings are cached, retrieved, and then deleted. A subsequent get call returns null.

Example 3:

Input:
cacheService.set('data', 'some data');
localStorage.clear(); // Simulate localStorage being cleared
cacheService.get('data').subscribe(data => { console.log(data); }); // Should log null

Output:

null

Explanation: Clearing localStorage removes the cached data, and get returns null.

Constraints

  • The service must be written in TypeScript.
  • The service must use localStorage for persistence.
  • Expiry times are in seconds.
  • The get method must return an Observable<any>.
  • The service should be reasonably performant. Avoid unnecessary computations or operations.
  • The maximum size of data stored in localStorage is limited by the browser. While you don't need to implement a complex eviction strategy, be mindful of this limitation.

Notes

  • Consider using RxJS operators like takeUntil to manage the lifecycle of the Observables returned by get.
  • Think about how to handle potential errors when accessing localStorage.
  • The expiry mechanism should be implemented using setTimeout.
  • Focus on clarity, readability, and maintainability of the code.
  • While perfect thread safety is difficult to achieve in JavaScript, consider potential race conditions and try to minimize them. Using localStorage directly is inherently not thread-safe, but the service's internal logic can be designed to reduce the likelihood of issues.
Loading editor...
typescript