Giter VIP home page Giter VIP logo

cashew's Introduction

Caching is nut a problem!


@ngneat/cashew MIT coc-badge commitizen PRs styled with prettier All Contributors ngneat

Features

βœ… HTTP Caching
βœ… State Management Mode
βœ… Local Storage Support
βœ… Handles Simultaneous Requests
βœ… Automatic & Manual Cache Busting
βœ… Hackable

A flexible and straightforward library that caches HTTP requests in Angular

Installation

$ npm install @ngneat/cashew

Usage

Use the provideHttpCache provider along with withHttpCacheInterceptor in your application providers:

import { provideHttpCache, withHttpCacheInterceptor } from '@ngneat/cashew';

bootstrapApplication(AppComponent, {
  providers: [provideHttpClient(withInterceptors([withHttpCacheInterceptor()])), provideHttpCache()]
});

And you're done! Now, when using Angular HttpClient, you can pass the withCache function as context, and it'll cache the response:

import { withCache } from '@ngneat/cashew';

@Injectable()
export class UsersService {
  constructor(private http: HttpClient) {}

  getUsers() {
    return this.http.get('api/users', {
      context: withCache()
    });
  }
}

It's as simple as that.

State Management Mode

When working with state management like Akita or ngrx, there is no need to save the data both in the cache and in the store because the store is the single source of truth. In such a case, the only thing we want is an indication of whether the data is in the cache.

We can change the mode option to stateManagement:

import { withCache } from '@ngneat/cashew';

@Injectable()
export class UsersService {
  constructor(private http: HttpClient) {}

  getUsers() {
    return this.http.get('api/users', {
      context: withCache({
        mode: 'stateManagement'
      })
    });
  }
}

Now instead of saving the actual response in the cache, it'll save a boolean and will return by default an EMPTY observable when the boolean resolves to true. You can change the returned source by using the returnSource option.

Local Storage

By default, caching is done to app memory. To switch to using local storage instead simply add:

import { provideHttpCache, withHttpCacheInterceptor, provideHttpCacheLocalStorageStrategy } from '@ngneat/cashew';

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(withInterceptors([withHttpCacheInterceptor()])),
    provideHttpCache(),
    provideHttpCacheLocalStorageStrategy()
  ]
});

To your providers list. Note that ttl will also be calculated via local storage in this instance.

Versioning

When working with localstorage, it's recommended to add a version:

import { withCache } from '@ngneat/cashew';

@Injectable()
export class UsersService {
  constructor(private http: HttpClient) {}

  getUsers() {
    return this.http.get('api/users', {
      context: withCache({
        version: 'v1',
        key: 'users'
      })
    });
  }
}

When you have a breaking change, change the version, and it'll delete the current cache automatically.

Config Options

Using the library, you might need to change the default behavior of the caching mechanism. You could do that by passing a configuration to the provideHttpCache function:

bootstrapApplication(AppComponent, {
  providers: [provideHttpClient(withInterceptors([withHttpCacheInterceptor()])), provideHttpCache(config)]
});

Let's go over each of the configuration options:

strategy

Defines the caching behavior. The library supports two different strategies:

  • explicit (default) - only caches API requests that explicitly use the withCache function
  • implicit - caches API requests that are of type GET and the response type is JSON. You can change this behavior by overriding the HttpCacheGuard provider. (See the Hackable section)
bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(withInterceptors([withHttpCacheInterceptor()])),
    provideHttpCache({ strategy: 'implicit' })
  ]
});

ttl

Define the cache TTL (time to live) in milliseconds: (defaults to one hour)

bootstrapApplication(AppComponent, {
  providers: [provideHttpClient(withInterceptors([withHttpCacheInterceptor()])), provideHttpCache({ ttl: number })]
});

responseSerializer

By default, the registry returns the original response object. It can be dangerous if, for some reason, you mutate it. To change this behavior, you can clone the response before getting it:

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(withInterceptors([withHttpCacheInterceptor()])),
    provideHttpCache({
      responseSerializer(body) {
        return cloneDeep(body);
      }
    })
  ]
});

API

WithCache

Currently, there is no way in Angular to pass metadata to an interceptor. The withCache function uses the params object to pass the config and removes it afterward in the interceptor. The function receives four optional params that are postfixed with a $ sign so it'll not conflicts with others:

  • cache - Whether to cache the request (defaults to true)
  • ttl - TTL that will override the global
  • key - Custom key. (defaults to the request URL including any query params)
  • bucket - The bucket in which we save the keys
  • version - To use when working with localStorage (see Versioning).
  • clearCachePredicate(previousRequest, currentRequest) - Return true to clear the cache for this key
  • context - Allow chaining function call that returns an HttpContext.
import { requestDataChanged, withCache } from '@ngneat/cashew';

@Injectable()
export class UsersService {
  constructor(private http: HttpClient) {}

  getUsers() {
    return this.http.get('api/users', {
      context: withCache({
        withCache: false,
        ttl: 40000,
        key: 'users',
        clearCachePredicate: requestDataChanged
      })
    });
  }
}

When you need to call another function that returns an HttpContext, you can provide the context option.

import { withCache } from '@ngneat/cashew';
import { withLoadingSpinner } from '@another/library'; // <-- function that returns an HttpContext

@Injectable()
export class TodosService {
  constructor(private http: HttpClient) {}

  getTodos() {
    return this.http.get('api/todos', {
      context: withCache({
        context: withLoadingSpinner()
      })
    });
  }
}

CacheManager

The CacheManager provider, exposes an API to update and query the cache registry:

  • get<T>(key: string): HttpResponse<T> - Get the HttpResponse from the cache
  • has(key: string) - Returns a boolean indicates whether the provided key exists in the cache
  • set(key: string, body: any, { ttl, bucket }) - Set manually a new entry in the cache
  • delete(key: string | CacheBucket) - Delete from the cache

CacheBucket

CacheBucket can be useful when we need to buffer multiple requests and invalidate them at some point. For example:

import { withCache, CacheBucket } from '@ngneat/cashew';

@Injectable()
export class TodosService {
  todosBucket = new CacheBucket();

  constructor(
    private http: HttpClient,
    private manager: HttpCacheManager
  ) {}

  getTodo(id) {
    return this.http.get(`todos/${id}`, {
      context: withCache({
        bucket: this.todosBucket
      })
    });
  }

  invalidateTodos() {
    this.manager.delete(this.todosBucket);
  }
}

Now when we call the invalidateTodos method, it'll automatically delete all the ids that it buffered. CacheBucket also exposes the add, has, delete, and clear methods.

Hack the Library

  • HttpCacheStorage - The storage to use: (defaults to in-memory storage)
abstract class HttpCacheStorage {
  abstract has(key: string): boolean;
  abstract get(key: string): HttpResponse<any>;
  abstract set(key: string, response: HttpResponse<any>): void;
  abstract delete(key?: string): void;
}
  • KeySerializer - Generate the cache key based on the request: (defaults to request.urlWithParams)
export abstract class KeySerializer {
  abstract serialize(request: HttpRequest): string;
}
  • HttpCacheGuard - When using the implicit strategy it first verifies that canActivate is truthy:
export abstract class HttpCacheGuard {
  abstract canActivate(request: HttpCacheHttpRequestRequest): boolean;
}

It defaults to request.method === 'GET' && request.responseType === 'json'.

  • TTLManager - A class responsible for managing the requests TTL:
abstract class TTLManager {
  abstract isValid(key: string): boolean;
  abstract set(key: string, ttl?: number): void;
  abstract delete(key?: string): void;
}

Compatability matrix

Cashew Angular
^4.0.0 ^17.0.0
3.1.0 >13.0.0 < 17
3.0.0 ^13.0.0
^2.0.0 ^12.0.0
^1.0.0 ^10.0.0

Contributors ✨

Thanks go to these wonderful people (emoji key):


Netanel Basal

πŸ’» 🎨 πŸ“– πŸ€” πŸš‡

Itay Oded

πŸ’»

Shahar Kazaz

πŸ’»

Lars Gyrup Brink Nielsen

πŸ“–

RaΓ­ Siqueira

πŸ–‹

Inbal Sinai

πŸ’» πŸ“–

James Manners

πŸ’»

mokipedia

πŸ’» πŸ“–

This project follows the all-contributors specification. Contributions of any kind welcome!

cashew's People

Contributors

fabiendehopre avatar fasidongit avatar gili-lumigo avatar itayod avatar jmannau avatar laurens-makel avatar laurensmakel avatar layzeedk avatar ledzz avatar mokipedia avatar muuvmuuv avatar netanelbasal avatar raisiqueira avatar shaharkazaz avatar theblushingcrow avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cashew's Issues

Angular Universal - localstorage is undefined

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

When using it with Angular Universal the localStorage is undefined.

Environment

Angular version: 12.0.4

Allow numeric values as keys

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[X] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

The following is not accepted syntax

const someId = 0;
withCache({ key: someId })

forcing me to use withCache({ key: `${someId}` }) or withCache({ key: ''+someId })

Expected behavior

This syntax to be accepted

const someId = 0;
withCache({ key: someId })

Minimal reproduction of the problem with instructions

What is the motivation / use case for changing the behavior?

Developer experience

Environment


Angular version: X.Y.Z


Browser:
- [ ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: XX  
- Platform:  

Others:

Allow override of cache storage provider

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[x] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

I am not able to change the storage provider from localStorage to something else

Expected behavior

A exposed HttpCacheStorage provider to modify it. We for example use CapacitorStorage.

Minimal reproduction of the problem with instructions

What is the motivation / use case for changing the behavior?

Environment


Angular version: 14


Browser:
- [ ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: 17  
- Platform:  Mac 

Others:

How to chain multiple HttpContext

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[X] Support request
[ ] Other... Please describe:

Current behavior

When you use the withCache() function in a HttpClient method, it needs to be assigned to the context option. How do you add something else to the HttpRequest context ?

Feature: specify max age for cached responses

Current behavior

Cached responses have a fixed expiry date, expressed in the form of a time-to-live which is added to the date/time the response is saved to the cache. If I make a request with a ttl of one hour, then request the same url five minutes later with a ttl of one minute, the cached response is returned and continues to be returned for a further 55min. The current behaviour honours the concept of time-to-live and is similar to the browser cache but it doesn't suit all use cases.

Expected behavior

I expected the ttl to be updated when the same url is requested with a different ttl. I also expected the cached response to be deleted when the requested ttl is less than the elapsed time since the last response was cached.

The above is one way to implement my desired behaviour, another would be to specify cache "maxAge" in milliseconds or "since" date/time in addition to ttl. If max age is specified and ttl is very large, responses would effectively be cached indefinitely, only being deleted when a more recent response is requested.

What is the motivation / use case for changing the behavior?

I'm working on a dashboard app with back-end data that is updated in bursts. I request aggregate data with a ttl of 1 hour because it is expensive to compute, but I also monitor the back-end for new data every 5 mins. When new data is received on the back-end I would like to lower the ttl/maxAge until the burst is complete, then gradually increase it again. There are multiple pages of views and corresponding queries that can be performed, so expiring the entire cache in one hit would not be a good strategy.

Question: How to use with Headers?

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[x] Support request
[ ] Other... Please describe:

Current behavior

I currently use a standard request to an API that needs Headers:

return this.http.get<ITideResults>(this.tideServiceURL, { headers: this.headers })

Expected behavior

Expected to be able to continue to use Headers as well as caching :-)

Minimal reproduction of the problem with instructions

Following the docs I tried:

return this.http.get<ITideResults>(this.tideServiceURL, { headers: this.headers }, withCache())

however realise get() expects 2 arguments and not 3. Not sure how to work out where to put withCache() if I'm also using headers.

What is the motivation / use case for changing the behavior?

The API I'm using has usage limits so I'd like to cache the request (for 7 days) on the users device on the first use so as to limit hitting the API ... Cashew seems to be a great option for this use case ;-)

Environment


Angular version: 9.1.0
@ngneat/cashew 1.1.4

Browser:
- [x ] Chrome (desktop) version 80.0.3987.163
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX

For Tooling issues:
- Node version: v12.13.0
- Platform:  Mac

Skip auto refresh cache after invalidate

I suggest skip auto repeat request after invalidate the cache until first call by user the call.


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[x] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

We got a repeat all requests after delete some key/bucker from the cache manager.

Expected behavior

Add config for prevent auto repeat cached requests

Enviroment

Angular version: 9.1.2
Cashew version: 1.3.2

HttpParams not being parsed on http Post correctly

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ X] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

When doing a http POST with HttpParams calls wrong url.

The called url is
https://localhost:4200/api/search?updates=%5Bobject%20Object%5D&cloneFrom=&encoder=%5Bobject%20Object%5D&map=null

Expected behavior

Can pass a variable with HttpParams type into WithCache and correct url is called. HTTP call functions as normal. Cache is stored as per cashew localstorage.

Minimal reproduction of the problem with instructions

Given a this test code.

    ...
    params = new HttpParams();
    params = params.append('context', context)
    
    return new Promise((resolve, reject) => {
      this.http.post(url, payload, withCache({...params})).subscribe((result) => {
        resolve(result);
      }, (error) => {
        reject(error);
      });
    });

Cashew clearly tries to convert HttpParams to query string. Is there a specific object I should be passing?

Environment


Angular version: X.Y.Z
8.2.3

Browser:
- [X ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: XX  
- Platform:  

Others:
Using Local Storage

Add reload$ property to the withCache params interface

Would it make sense to add reload$ property to the withCache params interface? When it will be set to true, stored data for the specific keys will be replaced with the newest. Now probably the one way to achieve that is manually removing a specific key with CacheManager before making a request.

withCache can not add Authorization header

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

Can not add HTTP header with withCache

Expected behavior

Minimal reproduction of the problem with instructions

What is the motivation / use case for changing the behavior?

export interface HttpOptions {
    headers: HttpHeaders;
    params?: HttpParams;
}

I think this kind of interface would be better to easy adding headers or parms with withCache function

Environment

Angular version: 11.2.9

Browser:

  • Chrome (desktop) version XX
  • Chrome (Android) version XX
  • Chrome (iOS) version XX
  • Firefox version XX
  • Safari (desktop) version XX
  • Safari (iOS) version XX
  • IE version XX
  • Edge version XX

For Tooling issues:

  • Node version: 14
  • Platform: Windows 10

Others:

Circular source file mapping dependency under Angular 10

I'm submitting a..

[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

Warning: Unable to fully load /Users/x/WebstormProjects/project/node_modules/@ngneat/cashew/lib/httpCacheStorage.d.ts 
for source-map flattening: Circular source file mapping
dependency:/Users/x/WebstormProjects/project/node_modules/@ngneat/cashew/lib/httpCacheStorage.d.ts.map -> 
/Users/x/WebstormProjects/project/node_modules/@ngneat/cashew/lib/httpCacheStorage.d.ts.map

Expected behavior

No warns

Minimal reproduction of the problem with instructions

  1. Just run npm run start with "@ngneat/cashew": "^1.3.0", in the package.json when using Angular 10.

Environment

Angular version: 10.0.8

Browser:

  • Chrome (desktop) version 85
  • Chrome (Android) version XX
  • Chrome (iOS) version XX
  • Firefox version XX
  • Safari (desktop) version XX
  • Safari (iOS) version XX
  • IE version XX
  • Edge version XX

For Tooling issues:

  • Node version: 12.14.1
  • Platform: Mac

Update docs on how to configure cashew in module based Angular app

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[ ] Feature request
[x] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

It seems that v4 dropped support for module based apps: 3e92e8e

It does not expose HttpCacheInterceptorModule and useHttpCacheLocalStorage anymore. And the docs have been updated on how to use cashew in standalone Angular apps. There are no docs on how to use cashew in non-standalone apps.

Expected behavior

The docs describe how to use cashew in non-standalone module based angular apps.

What is the motivation / use case for changing the behavior?

Many (most?) Angular apps are not standalone and therefore they should be supported.

Environment

@ngneat/cashew 4.0.0

[Documentation request] Interactions with fakeAsync and HttpClientTestingModule

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[ ] Feature request
[X] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

Currently it's not quite easy to figure out how Cashew will behave in tests. What it's interactions are with HttpClientTestingModule, and verifications on this. It is also not clear how it will behave in regards to fakeAsync, tick and their interactions with TTL.

Expected behavior

Some documentation about these interactions would be most helpful in testing code that relies on Cashew.

What is the motivation / use case for changing the behavior?

When testing code using cashew and using HttpClientTestingModule it can be hard to know exactly how to use the primitives such as flush, and how to properly do assertions on the HttpTestingController

feat: add modern provide* api-s

I'm submitting a...


[x] Feature request

Current behavior

Currently we have to use importProvidersFrom(HttpCacheInterceptorModule.forRoot()) in order to use the lib on apps that are built with standalone bootstrap api-s.

Also, we need to use withInterceptorsFromDi() in the provideHttpClient() in order for the cache interceptor to be registered as it's a class based interceptor.

Expected behavior

Add some new api-s that export the providers using sth like:

provideHttpCache()

And inside it we can add all the providers needed for the lib to work,
So basically we can just add sth like this (that we also can reuse in the module that the lib already uses, so no duplicated code):

export const provideHttpCache = (config: Partial<HttpCacheConfig> = {}) => {
  const providers: (Provider | EnvironmentProviders)[] = [
    { provide: HTTP_CACHE_CONFIG, useValue: { ...defaultConfig, ...config } },
    { provide: KeySerializer, useClass: DefaultKeySerializer },
    { provide: HttpCacheStorage, useClass: DefaultHttpCacheStorage },
    { provide: TTLManager, useClass: DefaultTTLManager },
    { provide: HttpCacheGuard, useClass: DefaultHttpCacheGuard },
    { provide: HttpCacheVersions, useClass: DefaultHttpVersions },
    HttpCacheManager,
    RequestsQueue,
    RequestsCache,

    config.skipInterceptorDeclaration 
      ? [] 
      : provideHttpClient(withInterceptors([httpCacheInterceptor])) // --> This is not recommended. 
      
     config.skipInterceptorDeclaration 
      ? [] 
      : { provide: HTTP_INTERCEPTOR_FNS, useValue: httpCacheInterceptor, multi: true } // --> Maybe we can do this in the future
  ];

  return makeEnvironmentProviders(providers);
};

What is the motivation / use case for changing the behavior?

Better align with the direction of Angular libs.

params array

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x ] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

const params = {brands: [1,2]};
this.http.get (url, {... {params: {... params}}});
URL?brands=1&brands = 2

Expected behavior

const params = {brands: [1,2]};
this.http.get (url, {... {params: {... params}}});
URL?brands=1

Minimal reproduction of the problem with instructions

const params = {brands: [1,2]};
this.http.get (url, {... {params: {... params}}});
URL?brands=1

What is the motivation / use case for changing the behavior?

Environment


Angular version: 8.2.14


Browser:
- [x ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: XX  10.21
- Platform:  Windows 

Others:
When I add an HttpCacheInterceptorModule to app.module.ts some of my requests stop working correctly,
namely params array cease to work correctly

"@angular/common": "~ 8.2.14",
example:

const params = {brands: [1,2]};
this.http.get (url, {... {params: {... params}}});
url?brands=1&brands=2

with HttpCacheInterceptorModule
url?brands=1

Bug: empty observable returned when request is repeated inside it's subscriber

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

This bug was first reported here: https://issueexplorer.com/issue/ngneat/cashew/27
I have narrowed it down further.

Current behavior

Cacheable requests are saved in a queue by the http interceptor. When a response is received it is shared with all subscribers and then, when the request completes, it is deleted from the queue inside a finalize operator so subsequent requests will be served from the cache.

But, if the request is called again by a subscriber of the initial request, the queued request is returned as an empty observable, after the response has been emitted but before it completes.

Expected behavior

The http interceptor should return the cached response, not the queued request, after the response is received.

Minimal reproduction of the problem with instructions

It comes down to this:

this.http.get(url).pipe(
  switchMap(() => this.http.get(url))
).subscribe(() => console.log(`Never happens`))

I will submit a pull request with a demonstration of this bug and a suggested fix.

What is the motivation / use case for changing the behavior?

This situation typically comes up when using cacheable http requests in guards and route handlers. This was the case for me and the two people who posted on issueexplorer.

Environment


Angular version: 12.0.1, 12.2.8, 10.2.3
cashew versions 1.3.1, 2.3.1


Browser:
- [x] Chrome (desktop) version 94
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [x] Firefox version 93
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 

docs: cache then refresh data

I'm submitting a...

[x] Documentation issue or request

Current behavior

Cashew caches HTTP requests and only updates the cached data when it is deleted/expired.

Expected behavior

Cashew retrieves cached data, returns it to the user, and then makes an HTTP request to update the cached data. Once the updated data is available, Cashew pushes it to the user.

What is the motivation / use case for changing the behavior?

I am using @ngneat/cashew to cache HTTP requests in my Angular project.

I would like to know if it is possible to implement the cache and refresh strategy, as exemplified on the angular.io page link

I tried looking for information in the documentation and code, but I could not find any.

// cache-then-refresh
if (req.headers.get('x-refresh')) {
  const results$ = sendRequest(req, next, this.cache);
  return cachedResponse ?
    results$.pipe( startWith(cachedResponse) ) :
    results$;
}
// cache-or-fetch
return cachedResponse ?
  of(cachedResponse) : sendRequest(req, next, this.cache);

Environment


Angular version: 15.2.2


Browser:
- [x] Edge version 118
 
For Tooling issues:
- Node version: v18.17.0  
- Platform: Windows 

Lib dont work in prod mod

I'm submitting a...


[ ] Regression
[x] Bug report 
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

When you build a project with the ng build --prod command, the library stops working.

Expected behavior

Library caches queries in prod mode

Minimal reproduction of the problem with instructions

AOT:true
ivy:false


    HttpCacheInterceptorModule.forRoot({
      strategy: 'explicit',
      ttl: 10 * 10000,
      responseSerializer(body) {
        return cloneDeep(body);
      }
    })


    return this.http.get(this.API + '/auth/user/', withCache({
      cache$: true,
      key$: USER_CACHE_KEY
    }))

Environment


Angular version: 8.0.1

Browser:
- [x] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [x] Firefox version XX
- [x] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: XX  
- Platform:  

Others:

Angular CLI reports library as CommonJS or AMD dependency

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[X] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

Angular CLI reports that @ngneat/cashew has a CommonJS or AMD dependency that can cause optimization bailouts.

Warning: C:\Projects\XYZ\src\app\core\core.module.ts depends on '@ngneat/cashew'. CommonJS or AMD dependencies can cause optimization bailouts.
For more info see: https://angular.io/guide/build#configuring-commonjs-dependencies

Expected behavior

I guess that Angular CLI shouldn't complain about @ngneat/cashew being a CommonJS or AMD dependency.

Minimal reproduction of the problem with instructions

I just updated cashew from last 1.x version latest 2.x version and Angular CLI started to produce this warning.

Environment


Angular version: 12.1.1
Angular CLI version 12.1.1

Browser:
- [ ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: 14.15.5  
- Platform:  Windows

Others:

docs: compatability matrix?

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[ ] Feature request
[X ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

I've been looking for a compatability matrix which would map the library's version against the supported Angular versions.

Expected behavior

Something like the following, this example is taken from RxAngular library.

image

Minimal reproduction of the problem with instructions

None

What is the motivation / use case for changing the behavior?

Help new users of the library to pick the correct version for their application.

Environment


Angular version: X.Y.Z


Browser:
- [ ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: XX  
- Platform:  

Others:

Feature: Dynamic storage target

I'm submitting a...


[x] Other... Please describe:

Current behavior

Currently you have to decide overall what the storage pattern will be (whether memory, local storage, custom, etc...)

Possible behavior?

I was curious if anyone has asked for the ability to pass a storage parameter in the withContext so you could declare a storage target specific to different API calls.

The reason I bring this up is that there might be some non sensitive API calls that return application configuration that would be a good candidate for localStorage where I get the benefit of multiple browser tabs sharing that information. Whereas I might have some other data I'd like to cache in the browser but just inMemory that I wouldn't want to leave in localStorage.

Perhaps the answer would be to write a custom storage provider but I was just curious if anyone had asked for something like this or if there's a strong reason why its very difficult or not a good idea.

Great library btw. Thanks for your time!

Requests that error remain in cache and cannot be cleared

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

If an http request errors (any 400/500 response), the request remains in the queue and cannot be cleared but emptying the cache.

Expected behavior

And non successful response should not be cached and should be cleared from the queue.

Minimal reproduction of the problem with instructions

  1. Issue a http request that will error. ie:
this.http.get('http://notarealurl.test', withCache())
  1. Check the httpCacheManage queue. The request remains in the queue. This means if the request is retried the original request is replayed.

What is the motivation / use case for changing the behavior?

For instance, when requesting an endpoint that is protected by authorisation, the first user may not be able to access the resource (returning 403). This user logs out and another user logs in. The request is retried, but the original request is issued including the Authorization header from user 1, not the new request with the new Authorization header.

Suggested Fix

I suggest changing the interceptor like this (https://github.com/ngneat/cashew/blob/master/projects/ngneat/cashew/src/lib/httpCacheInterceptor.ts#L37):

tap({
    next: event => {
          if (event instanceof HttpResponse) {
            const cache = this.httpCacheManager._resolveResponse(event);
            this.httpCacheManager._set(key, cache, +ttl);
           }
    },
    // Delete the item from the queue when the request completes
    // This will ensure it is not replayed if the request is cancelled or it errors
    complete: () => {
        queue.delete(key);
    }
}),

Environment


Angular version: 9.1.3
@ngneat/cashew 1.1.3 


Browser:
- [x] Chrome (desktop) version 81
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: 12 
- Platform:  Mac

Others:

Current example of ParameterEncoder override in config breaks AOT compilation

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

Currently parameterCodec in config as it is shown in the readme does not work fΓΌr AOT compilation. The problem is the constructor call of new CustomHttpParamEncoder() in @NgModule.

Expected behavior

possibility to give CustomHttpParamEncoder to config so the request params are correctly encoded / decoded.

Minimal reproduction of the problem with instructions

add parameterEncoder to config and compile with AOT.

Environment


Angular version: 7,8,9


Browser:
- [ ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: v14.1.0  
- Platform:  Mac

Others:

Angular 14 support

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[X] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

Angular 13 is used for the project currently.
The README.md doesn't give any information about supported angular versions

Expected behavior

I am aware that the library package.json has a peer dependency towards angular 13+ - so it's possible the library works just fine with angular 14 (which is really cool and should be adopted by more angular 3rd party libs πŸ‘ )

But I'm currently mapping out the migration path for a large project with a lot of dependencies, and I was wondering if cashew has already been tested with angular 14? Maybe somebody has experience migrating this already?

Then I thought it would be nice to actually provide angular compatiblity information in the main readme of the repository.

Alternatively upgrading the whole library to angular 14 would also resolve my question, of course 😁

Minimal reproduction of the problem with instructions

n/a

What is the motivation / use case for changing the behavior?

Angular 14 was released on June 02, 2022 and is becoming adopted more and more widely. In November we already expect ng15 to hit the road.
Cashew is one of the last libraries for my specific ng 14 upgrade that is not confirmed yet to be ng 14 compatible.

Environment

Angular 14

Regression: Implicit strategy broken in v2?

Implicit strategy seems to require using withCache()

In 2.0.1 cache-interceptor.ts has this:

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const context = request.context.get(CACHE_CONTEXT);

    if (context === undefined) {
      return next.handle(request);
    }
...

If I have not misunderstood the logic of the code, this will always return the request unchanged unless you use withCache().

In 1.3.2 httpCacheInterceptor.ts had a different logic and starts asking CacheGuard.

TypeError when testing service with CacheBucket

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

The TypeScript compiler throws a TypeError when one is testing a service that uses the CacheBucket.

Example service:

@Injectable()
export class DocumentService {

  private _documentBucket = new CacheBucket();

  constructor(private http: HttpClient) {}

  /**
   * Returns a cached document
   * @param documentId
   */
  getDocument(documentId: string): Observable<EbDocument> {
    return this.http.get<EbDocument>(`/api/documents/${documentId}`, {
      context: withCache({
        ttl: 10_000,
        bucket: this._documentBucket
      })
    });
  }
}

Which causes the following in a Spectator driven test that is executed by Jest (we do not use the createHttpFactory here since we explicitly want to validate the cache context):

describe('DocumentService', () => {
  const createService = createServiceFactory<DocumentService>({
    service: DocumentService,
    mocks: [HttpClient]
  });

  it('should call the endpoint', done => {
    const spectator = createService();
    const httpClient = spectator.inject(HttpClient);
    httpClient.get.andReturn(of({}));

    spectator.service
      .getDocument('documentId')
      .subscribe(() => {
        expect(httpClient.get).toHaveBeenCalledWith('/api/documents/documentId', {
          context: withCache({
            ttl: 10_000,
            bucket: new CacheBucket()
          })
        });
        done();
      });
  });
});
TypeError: Constructor Set requires 'new'
        at CacheBucket.Set (<anonymous>)

Since CacheBucket only extends Set without applying anything else, it is possible to fix this issue by typing the CacheBucket as Set here, but this is dangerous since the implementation may change over time.

Expected behavior

We should fix this, so we have a happy compiler.

Minimal reproduction of the problem with instructions

Angular >= 12 with TypeScript 4.3.5 and Cashew 2.3.2.

What is the motivation / use case for changing the behavior?

Environment


Angular version: 12.2.16


Browser:
- [ ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: XX  
- Platform:  

Others:

Use withCache() in HttpClient.request()

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[x] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

I've seen in the doc that you can use withCache() in HttpClient methods like .get(), .post(), etc. But when trying to be used on .request() https://angular.io/api/common/http/HttpClient#request, is not supported (or maybe I have not found the way)

Expected behavior

Support to use withCache() on .request() method

What is the motivation / use case for changing the behavior?

Being able to use cashew with an abstraction method to do http requests

Bug: When using LocalStorage for cache, clear() does not work

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

I'm using useHttpCacheLocalStorage to cache with LocalStorage and the request results are being cached with key '@cache-example'.
The culprit seems to theese 2 lines, where i think createKey() there shoud not be there:
image

When I try to clear all cached entries with HttpCacheManager clear(), it iterates all the keys and ads a new '@cache' prefix making the previous key @cahe-@cache-example:

image

Expected behavior

Localstorage entries related to cached data should have been deleted.

Minimal reproduction of the problem with instructions

It is possible to reproduce using the playground app with useHttpCacheLocalStorage.

Environment


Angular version: 14.0.1
Cashew version: 3.0.0


Browser:
- [x] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX

HttpHeaders are not handled

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

I am not seeing HttpHeaders beeing saved or received in cache.

Expected behavior

HttpHeaders should be serialized as HttpHeaders otherwise a .get() wont longer work in interceptors.

Minimal reproduction of the problem with instructions

What is the motivation / use case for changing the behavior?

We have request and correlation ids set in backend in response headers that I put into the response body for better access in templates. This is done in an interceptor.

Environment


Angular version: 17


Browser:
- [ ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: 20  
- Platform:  Mac 

Others:

Option to set HttpParams Encoder

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[ ] Performance issue
[X] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

Currently, new HttpParams is called hidden away inside the lib without the possibility to set the encoder

Expected behavior

being able to set an encoder (at least globally) as HttpParams default encoder does not encode/decode params as expected:
angular/angular#18261

Minimal reproduction of the problem with instructions

have symbols such as + in the param value

What is the motivation / use case for changing the behavior?

As the lib abstracts away the creation of http params, a way to correct the encoding is needed.

Environment


Angular version: 4+ (tested on 5,6,7,8,9)

Browser:
all browsers
 
For Tooling issues:
- Node version: all versions
- Platform:  tested on mac, linux, windows

Default values config using forRoot works in Dev but not in Prod (Aot)

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x ] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

The following code works fine in development mode (ng serve), but not in Prod mode (ng serve --prod=true):


 { provide: HTTP_CACHE_CONFIG, useValue: { ...defaultConfig, ...config } }

When I tried to change code as below, then I am losing config variable (but worked fine in --prod=true flag)


 { provide: HTTP_CACHE_CONFIG, useValue: { defaultConfig} }

Expected behavior

It must accepted both values in production mode.

Minimal reproduction of the problem with instructions

Run local with prod mode -
ng serve --prod=true , then monitor below methods (network call), it is calling API in each click event
loadTodos() {
this.http.get('https://jsonplaceholder.typicode.com/todos', withCache()).subscribe(res => {
console.log(res);
});
}

What is the motivation / use case for changing the behavior?

It should cache API calls in production mode as well

Environment


Angular version: 7.2.16


Browser:
- [x ] Chrome (desktop) version 84.0.4147.125
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: 12.16.1
- Platform:  Windows 

Others:
@angular-devkit/architect          0.13.10 (cli-only)       
@angular-devkit/build-angular      0.13.10
@angular-devkit/build-ng-packagr   0.13.10
@angular-devkit/core               7.3.10 (cli-only)        
@angular-devkit/schematics         7.3.10 (cli-only)        
@angular/cli                       7.3.10
@ngtools/json-schema               1.1.0
@ngtools/webpack                   7.3.10
@schematics/angular                7.3.10
@schematics/update                 0.13.10 (cli-only)       
ng-packagr                         4.7.1
rxjs                               6.5.5
typescript                         3.2.4
webpack                            4.29.0

Async Storage Option

#4 I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[x ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

HttpCacheStorage provides a synchronous interface.

Expected behavior

Allow for caching in async storage such as IndexedDB and other async storage options.

Minimal reproduction of the problem with instructions

What is the motivation / use case for changing the behavior?

I'm creating an Ionic/Capacitor application and I would like to be able to use Capacitor storage to work around the 5mb localStorage limit. I'm not able to do this because all of the Storage methods return promises.

Environment

All



Cache response is not returned in resolve

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[X] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Minimal reproduction of the problem with instructions

I have a route with a guard and a resolve. The same service is called in both and the called method is a HTTP request with caching enabled (useHttpCacheLocalStorage).

The service

  getLeagues(): Observable<League[]> {
    console.log('getLeagues')
    return this.http.get<League[]>(`${LEAGUES_API}`,
      withCache()
    )
  }

The resolve (the tap operator is just to understand why it's not working)

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<League[]> | League[]{
    console.log('call getLeagues from resolve')
    return this.leagueService.getLeagues()
      .pipe(
        tap(val => console.log(`${val}`)),
      );
  }

The behavior is wrong when cache is empty

Cache is empty
Call getLeagues from guard => cache is populated
Call getLeagues from resolve => the tap operator is not called

Refresh the app (cache is already populated)
Call getLeagues from guard
Call getLeagues from resolve => the tap operator is called

Environment


Angular version: 11.0.2


Browser:
- [X] Chrome (desktop) version Chromium to 87.0.4280.67
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.