Angular Persistent Cache Service
Modern web applications often benefit from caching data locally to improve performance and provide offline capabilities. This challenge focuses on creating a robust and persistent cache service in Angular that can store and retrieve data across browser sessions.
Problem Description
Your task is to design and implement an Angular service that acts as a persistent cache. This service should be able to store various types of data (e.g., strings, numbers, objects, arrays) associated with unique keys. Crucially, the cached data must persist even when the user closes and reopens their browser, leveraging the browser's localStorage or sessionStorage for persistence.
Key Requirements:
- Service Creation: Create an Angular service (e.g.,
PersistentCacheService). - Storage Mechanisms: Implement methods to store data using
localStorage(for session-independent persistence) and potentiallysessionStorage(for session-dependent persistence, thoughlocalStorageis the primary focus). - CRUD Operations:
setItem(key: string, value: any): void: Stores a value with a given key.getItem(key: string): any | null: Retrieves a value associated with a key. Returnsnullif the key doesn't exist or the data is invalid.removeItem(key: string): void: Removes a value associated with a key.clear(): void: Clears all cached items.
- Data Serialization/Deserialization: Ensure that complex data types (objects, arrays) can be correctly serialized (e.g., using
JSON.stringify) before storing and deserialized (e.g., usingJSON.parse) upon retrieval. - Error Handling: Implement graceful error handling for potential issues like exceeding storage limits or invalid data formats during retrieval.
- Type Safety: Use TypeScript to provide type safety for keys and values where appropriate.
Expected Behavior:
- Data stored using
setItemshould be retrievable usinggetItemafter the browser is closed and reopened. getItemshould returnnullfor keys that have not been set or have been removed.removeItemshould effectively delete the item from the cache.clearshould empty the entire cache.- If
localStorageis not available, the service should still function, but without persistence.
Edge Cases:
- Storage Limits:
localStoragehas a limited size (typically 5-10MB). The service should handle cases where attempting to store data might exceed this limit. - Invalid Data: If data stored in
localStoragebecomes corrupted or is not valid JSON (if it was intended to be),getItemshould returnnullor handle it gracefully without crashing the application. - Browser Support: Consider how the service behaves in browsers that might not support
localStorageor have it disabled.
Examples
Example 1:
// Assume PersistentCacheService is injected into a component or another service
const cacheService: PersistentCacheService = // ... injected service
// Storing data
cacheService.setItem('userName', 'Alice');
cacheService.setItem('userSettings', { theme: 'dark', notifications: true });
cacheService.setItem('lastLoginTimestamp', Date.now());
// Retrieving data
const userName = cacheService.getItem('userName'); // Expected: 'Alice'
const userSettings = cacheService.getItem('userSettings'); // Expected: { theme: 'dark', notifications: true }
const nonExistent = cacheService.getItem('nonExistentKey'); // Expected: null
// Removing data
cacheService.removeItem('userName');
const userNameAfterRemoval = cacheService.getItem('userName'); // Expected: null
// Clearing all data
cacheService.clear();
const allItemsAfterClear = cacheService.getItem('userSettings'); // Expected: null
Example 2:
Scenario: Persistence Check
- In the browser:
// In your Angular app cacheService.setItem('persistentData', { id: 123, message: 'Hello Persistence!' }); console.log('Data stored.'); - Close the browser tab/window.
- Reopen the browser and navigate to the same application URL.
- In the browser console:
// In your Angular app after reload const retrievedData = cacheService.getItem('persistentData'); console.log('Retrieved data:', retrievedData);
Expected Output in Console (after reload):
Retrieved data: { id: 123, message: 'Hello Persistence!' }
Example 3: Handling Storage Limits (Conceptual)
If the localStorage is nearly full and an attempt is made to store a very large object:
const largeData = Array(1000000).fill('some string data'); // Potentially very large
try {
cacheService.setItem('veryLargeData', largeData);
} catch (error) {
console.error('Failed to store large data:', error); // Expected to catch an error related to storage quota exceeded
}
The setItem method should ideally throw an error or return a boolean indicating failure, which the calling code can then handle. The getItem method for this key should return null.
Constraints
- The primary storage mechanism must be
localStorage. - The service must be designed as an Angular injectable service.
- All core operations (
setItem,getItem,removeItem,clear) must be implemented. - The solution should gracefully handle the absence of
localStoragein the browser environment, falling back to in-memory storage without persistence. - Performance for typical small to medium-sized data operations should be near-instantaneous. Operations involving larger data should not block the main thread for extended periods.
Notes
- Consider using a unique prefix for your cache keys to avoid potential conflicts with other
localStorageentries. - When storing objects or arrays,
JSON.stringifyandJSON.parseare your friends. Be mindful of what typesJSON.stringifycan handle (it cannot serialize functions orundefineddirectly within objects). - For handling storage limits, you might want to check the current size of
localStoragebefore attempting to set a new item, or rely on the browser'sQuotaExceededErrorexception. - Think about how to handle
nullorundefinedvalues explicitly stored in the cache. - A possible strategy for handling
localStorageunavailability is to wrap alllocalStorageoperations intry...catchblocks and fall back to an in-memory array or Map iflocalStorageis not available or throws an error.