npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

rxjs-response-cache

v1.0.1

Published

Global cache service for responses fetched by RxJS observables

Downloads

15

Readme

RxJS Response Cache

Have you ever struggled with efficiently caching API responses retrieved through observables?

It gets trickier, especially when dealing with dynamic filters like pagination in APIs. Or consider scenarios where you're confident that a response is unlikely to change during a user's visit to your website. However, deciding when to expire the cache becomes a challenge, potentially leading to users receiving outdated information or setting such a short expiration time that it becomes impractical.

In many cases, these challenges lead us to skip caching, resulting in less satisfying user experience and performance.

Now, let's dive into RxJS Response Cache and see how it streamlines caching.

The package automatically stores a diverse range of responses, assigning them unique keys generated from a combination of the URL and alphabetically sorted query parameters. This ensures that each distinct response is effortlessly stored and managed.

Also, with its use-stale-while-refreshing feature, not only does it reduce wait times, but it guarantees users consistently have the most up-to-date data at their fingertips.

Check the Live Demo

Main Features

  • Global accessibility throughout the application.
  • Uses stale data during refresh.
  • Accelerates data access.
  • Reduces network requests.
  • Simplifies prefetching.
  • Includes clear timeouts for precise caching control.
  • Integrated DevTool for visual cache event inspection.
  • Designed for easy use.

Document Main Sections

  • Main Features
  • Usage Examples
  • Usage in Angular
  • Cache Structure and Auto-Generated Keys
  • Determining When to Use a Unique Identifier
  • Prefetching
  • Cleaning the Data
  • Resetting the Cache
  • Updating the Cache
  • How Refreshing Works with RxJS Subscribers
  • Multiple Instances
  • Bulk Operations
  • Null Values in Query Params
  • Developer Tool
  • API Reference

Usage Examples

Install the package:

npm install rxjs-response-cache --save

or

yarn add rxjs-response-cache

Instantiate the cache service at the root of your application or any other location within the components tree.

import ResponseCache from 'rxjs-response-cache';

const cache = new ResponseCache({
   isDevMode: process.env.MODE === "development",
   devtool: {
      show: true,
   },
});

See Configuration Available Parameters

Supply it as needed and start using it as follows:

Please Note that you can use the get() method (which returns a new observable) in 2 ways:

  • Using arrangedUrl
  • Ignoring arrangedUrl

arrangedUrl is a part of the auto-generated key used by the service to store data. It's a combination of provided url, string query parameters (if they exist in url parameter), defaultParams and params. The values are alphabetically sorted, and empty strings, undefined, and null values are automatically removed (null value removal can be configured).

For a deeper understanding, refer to the Cache Structure and Auto-Generated Keys section.

Method 1 ( Using arrangedUrl ):

const getPosts = () => {
   return cache.get<Post[]>({
      url: "posts",
      defaultParams: {page: 1, "page-size": 20},
      params: urlParamsObject,
      observable: ({arrangedUrl}) => observable<Post[]>(arrangedUrl),
   }).pipe(your_operations);
}

Method 2 ( Ignoring arrangedUrl argument and working with your own data ):

const getPosts = () => {
   const url = "posts";
   const params = urlParamsObject;
   return cache.get<Post[]>({
      url: url,
      defaultParams: {page: 1, "page-size": 20},
      params: params,
      observable: () => observable<Post[]>(url, {params}),
   }).pipe(your_operations);
}

Read the following section to understand when to use each method?

Best practice: Chain the pipe()s to the get() method, not the passed observable. This ensures that the actual API response, not a potentially modified version, is stored in the cache, and prevents potential bugs when working with the same API but different operations in separate modules.

Important Hint: Ensure that you also provide the parameters (if they exist) to the get() method. This is essential as the service uses all query parameters to generate unique keys.

Additionally, to achieve the best possible results from the service, always include your API default parameters when they can be altered by the end-user. This prevents the generation of two different keys for /posts and /posts?page=1, even though they are essentially the same.

Read the Cache Structure and Auto-Generated Keys section for more details.

See Get Method Available Parameters

And then:

getPost().subscribe();

Determining When to Use Second Method

You may opt for the second method only when there's a specific requirement that is ignored in arrangedUrl. In arrangedUrl, all empty strings, undefined, and null values are automatically removed (ignoring null values can be configured). Additionally, duplicated query parameters are overwritten, and you should concatenate them with commas if you genuinely need all of them. If this behavior doesn't meet your needs, consider using the second method and work with your own data.

Usage Example in Angular

Hint: Ensure you have read the Usage Example section first.

import ResponseCache from 'rxjs-response-cache';

function cacheFactory() {
   return new ResponseCache({
      isDevMode: isDevMode(),
      devtool: your_desired_options,
   });
}

@NgModule({
   providers: [
      {provide: ResponseCache, useFactory: cacheFactory},
   ],
})

And start using it in your services:

getPosts = () => {
   return this.cache.get<Post[]>({
      url: "posts",
      observable: ({arrangedUrl}) => this._httpClient.get<Post[]>(arrangedUrl),
      ...other_params,
   });
}

And then in your components:

getPost().subscribe();

Cache Structure and Auto-Generated Keys

The cache is a map of auto-generated keys and the data. For example, a code snippet like this:

const getPosts = () => {
   return cache.get<Post[]>({
      url: "posts",
      defaultParams: {page: 1 },
      params: {
         page: url.query.page, 
         "start-date": some_date, 
         "end-date": some_date,
         "some-other-param": is_undefined_for_now 
      },
      observable: ({arrangedUrl}) => observable<Post[]>(arrangedUrl),
   }).pipe(your_operations);
}

Will update the cache to this:

const cache = {
   "posts? end-date=some_date & page=some_number & start-date=some_date": res
}

arrangedUrl passed as an argument to your observable is essentially this auto-generated key.

Please note that the query parameters are sorted and undefined value is removed.

Best practice: Chain the pipe()s to the get() method, not the passed observable. This ensures that the actual API response, not a potentially modified version, is stored in the cache, and prevents potential bugs when working with the same API but different operations in separate modules.

Determining When to Use a Unique Identifier

This value, if present, will be added to the auto-generated key for storing the data. In most cases (99.99%), it's unnecessary. Consider using it only if you must differentiate between two data types which are going to generate the exact same key.

Prefetching

Simply subscribe to your API handler, and the result will be stored in the cache for later use.

getPost().subscribe();

Cleaning the Data

The clean() method allows you to remove specific data or multiple entries from the cache.

Hint: if you used uniqueIdentifier, make sure to include it in the second parameter.

Note: The default behavior for queries is NOT based on an exact match.

Examples

Picture the cache in this state:

const cache = {
    "posts?page=1" : data,
    "posts?page=1&comments=true" : data,
    "posts?page=2": data,
    "tweaked_posts__posts?page=1" : tweakedData,
}

To clean all the keys containing "posts" & page=1 (matches the 2 first keys):

cache.clean('posts',{ queryParams: { page: 1} })

To clean one key, containing "posts" & page=1 (exact match):

cache.clean('posts',{ queryParams: { page: 1}, exact: true })

Please note that neither of the above examples removes the fourth and fifth keys because uniqueIdentifier is not included in the options.

To clean all the keys containing "posts" & uid=tweaked_posts (matches only the forth key):

cache.clean('posts',{ uniqueIdentifier: "tweaked_posts", queryParams: { comments: true} })

See Clean Method Available Parameters

Resetting the Cache

The reset() method clears the entire cache.

cache.reset();

Updating the Cache

Coming Soon: Update functionality is slated for the next minor version release!

How Refreshing Works with RxJS Subscribers

If the data is not present in the cache, subscriber.next() and subscriber.complete() are triggered when the request is resolved.

If the data is already present in the cache, subscriber.next() is immediately triggered with the stale data. By default, once the request is resolved, the newly fetched data is compared to the stale data. If they differ, subscriber.next() is invoked again with the fresh data, and ultimately, subscriber.complete() is triggered.

This equality check can be disabled in the configuration, causing subscriber.next() to be called twice, even if the data is identical to the cached version.

Please note that you should stop rendering spinners and skeletons into the next() function not the complete(), when using the refresh feature.

Multiple Instances

Using multiple instances of the service is supported, but the devtool should be used with one instance at a time.

Bulk Operations

The get() method returns a new observable, so use it with bulk operations as usual. Example:

const res = forkJoin({ foo: cache.get(), bar: cache.get() })

Null Values in Query Params

Null values are ignored from query parameters by default. This behavior can be changed in the cache configuration at instantiation.

See Configuration Available Parameters

Developer Tool

The integrated developer tool allows you to inspect the last state of the cache and its history of changes. Additionally, every event related to the cache will be logged in the tool.

See Devtool Available Parameters

API Reference

Configuration Parameters

| Name | Type | Description | |:----------------------------------|:----------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | isDevMode | boolean | In dev mode, clear timeout IDs will be stored in local storage to be cleared in possible hot-reloads. This ensures that the devtool does not display incorrect information from previous loads during development.Additionally, the devtool is available only in dev mode. | | paramsObjectOverwrites-UrlQueries | boolean [=true] | Determines how the service should behave if a query parameter is accidentally present in both the url parameter and the params parameter.Example: cache.get({url: "/posts?page=2", params: {page: 3}, observable:() => observable}) by default will be resolved to "/post?page=3". | | preventSecondCallIfDataIsUnchanged | boolean [=true] | Determines whether the observable.next() should be invoked again when the refreshed data is identical to the stale data.By default, the observable.next() is invoked only once in such cases, optimizing to prevent unnecessary rerenders in applications.If desired, you can pass false and perform your own check within your application.For a detailed explanation, please refer to the How Refreshing Works with RxJS Subscribers section. | | removeNullValues | boolean [=true] | Determines whether null values should be removed from query parameters or not. | | devtool | object [:?] | Developer tool configuration. See Devtool Available Parameters. |

Service Instance Methods & Properties

| Name | Type | Description | |:--------------|:---------|:-----------------------------------------------------------------------| | get() | function | Fetches data and stores the expected result in the cache. | | clean() | function | Allows you to remove specific data or multiple entries from the cache. | | reset() | function | Clears the entire cache. | | config | object | Configuration passed to the service. | | data | object | Stored data. | | observables | object | Stored observables. | | clearTimeouts | object | Active clear timeouts. |

Get Method Parameters

| Name | Type | Description | |:-----------------|:-----------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | url | string | The endpoint address (may include query parameters or not). | | observable | () => function | The callback function that returns an observable. It receives an object containing the arrangedUrl as input.See Cache Structure and Auto-Generated Keys for details. | | uniqueIdentifier | string [:?] | This value, if present, will be added to the auto-generated key for storing the data.See When to Use Unique Identifier . | | defaultParams | object [:?] | The API's default query parameters.To optimize cache results, ensure to include them if they can be altered by the end-user. | | params | object [:?] | The queryParams will overwrite the defaultParams, and by default (configurable), any query strings in the url parameter will also be overwritten. | | refresh | boolean [=false] | Determines if the data should be refreshed on the next calls or noDetermines if the data should refresh on subsequent calls.By default, the API will be called only once.Passing true is especially useful when you are unsure if the data will remain the same. This way, users receive the old data immediately and then see the newly fetched data if there are any changes. | | clearTimeout | number [?:] | The time in milliseconds used to remove the data from the cache. |

Clean Method Parameters

| Name | Type | Description | |:-------------------------|:-------------|:---------------------------------------------------------------------------------------------------------------------------------------------------| | url | string | The endpoint address (may include query parameters or not).DO NOT include the uniqueIdentifier part here. | | options | object [?:] | Extra options for cleaning. | | options.exact | boolean [?:] | Determines if the query should be based on an exact match or not. | | options.uniqueIdentifier | string [?:] | Unique identifier.Note: If the key includes a unique identifier, you should pass it here, even if the query is not based on an exact match. | | options.queryParams | object [?:] | Query Parameters. They will be sorted and truncated if they contain an empty string, undefined, or null (null is configurable). |

See Cleaning the data for examples.

Devtool Parameters

type DevtoolConfig = {
   show?: boolean; // default = isDevMode && true
   isOpenInitially?: boolean; // default = false
   styles?: {
      zIndex?: number; // default = 5000 
      toggleButtonPosition?: {
         right?: number; // default = 25
         bottom?: number; // default = 25
      };
   };
}