Hone logo
Hone
Problems

Implementing a Runtime Cache in Angular

Runtime caching is a powerful technique for optimizing Angular applications by storing frequently accessed data in memory, reducing the need for repeated API calls or expensive computations. This challenge asks you to build a reusable runtime caching service that can be integrated into Angular components to improve performance and user experience. The service should handle caching, invalidation, and retrieval of data based on a provided key.

Problem Description

You need to create an Angular service called RuntimeCacheService that provides runtime caching functionality. The service should allow components to store data associated with a unique key, retrieve data by that key, and invalidate the cache for a specific key.

Key Requirements:

  • set(key: string, data: any): Stores the provided data in the cache, associated with the given key. If a key already exists, it should be overwritten.
  • get(key: string): Observable<any>: Retrieves data associated with the given key. It should return an Observable that emits the cached data if found. If the key is not found, the Observable should complete (not emit an error).
  • invalidate(key: string): Removes the entry associated with the given key from the cache.
  • clear(): Removes all entries from the cache.
  • Thread Safety: The cache should be thread-safe, meaning multiple components accessing the cache concurrently should not lead to data corruption or unexpected behavior. (While Angular itself runs in a single thread, consider future extensibility or potential use in server-side rendering scenarios).
  • Error Handling: The get method should gracefully handle cases where the key is not found in the cache, completing the observable instead of throwing an error.

Expected Behavior:

  • When set is called, the data should be stored in the cache.
  • When get is called with a key that exists in the cache, the cached data should be emitted immediately.
  • When get is called with a key that does not exist in the cache, the Observable should complete without emitting any value.
  • When invalidate is called, the corresponding entry should be removed from the cache. Subsequent get calls with the invalidated key should complete.
  • When clear is called, all entries in the cache should be removed.

Edge Cases to Consider:

  • Empty keys: Handle cases where the provided key is an empty string or null/undefined. You can either throw an error or treat it as a no-op (do nothing).
  • Large data sets: Consider the potential memory usage if the cache grows very large. While this challenge doesn't require eviction policies, be mindful of the implications.
  • Concurrent access: Ensure the cache is thread-safe to prevent data corruption.

Examples

Example 1:

Input:
  cache.set('user', { id: 1, name: 'John Doe' });
  cache.get('user').subscribe(data => console.log(data));
Output:
  { id: 1, name: 'John Doe' }
Explanation: The 'user' data is stored and then retrieved successfully.

Example 2:

Input:
  cache.set('user', { id: 1, name: 'John Doe' });
  cache.get('product').subscribe(data => console.log(data));
Output:
  (Observable completes without emitting a value)
Explanation: The 'product' key does not exist in the cache, so the Observable completes.

Example 3:

Input:
  cache.set('user', { id: 1, name: 'John Doe' });
  cache.invalidate('user');
  cache.get('user').subscribe(data => console.log(data));
Output:
  (Observable completes without emitting a value)
Explanation: The 'user' entry is invalidated, and subsequent `get` calls complete.

Constraints

  • The service must be written in TypeScript and compatible with Angular.
  • The get method must return an Observable.
  • The cache should be implemented using a JavaScript Map for efficient key-value storage.
  • The service should be designed to be reusable across multiple Angular components.
  • The service should be reasonably performant for typical caching scenarios. Avoid unnecessary overhead.

Notes

  • Consider using RxJS operators to handle the Observable returned by get.
  • Think about how to ensure thread safety when accessing and modifying the cache. While Angular's single-threaded nature mitigates some concerns, designing for potential future use cases is beneficial.
  • This challenge focuses on the core caching functionality. Advanced features like time-based expiration, eviction policies, or cache dependencies are beyond the scope of this exercise.
  • Focus on clarity, maintainability, and correctness in your implementation. Good error handling and edge case management are important.
Loading editor...
typescript