Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
@ngneat/cashew
Advanced tools
A simple and flexible library that caches HTTP requests in Angular applications
Caching is nut a problem!
✅ 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
$ npm install @ngneat/cashew
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.
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.
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.
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.
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
functionimplicit
- 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);
}
})
]
});
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 globalkey
- Custom key. (defaults to the request URL including any query params)bucket
- The bucket in which we save the keysversion
- To use when working with localStorage
(see Versioning).clearCachePredicate(previousRequest, currentRequest)
- Return true
to clear the cache for this keycontext
- 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()
})
});
}
}
The CacheManager
provider, exposes an API to update and query the cache registry:
get<T>(key: string): HttpResponse<T>
- Get the HttpResponse
from the cachehas(key: string)
- Returns a boolean
indicates whether the provided key
exists in the cacheset(key: string, body: any, { ttl, bucket })
- Set manually a new entry in the cachedelete(key: string | CacheBucket)
- Delete from the cacheCacheBucket
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.
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;
}
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 |
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!
FAQs
A simple and flexible library that caches HTTP requests in Angular applications
The npm package @ngneat/cashew receives a total of 15,325 weekly downloads. As such, @ngneat/cashew popularity was classified as popular.
We found that @ngneat/cashew demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.