Skip to content

Commit

Permalink
Merge branch 'feat-key-prefix'
Browse files Browse the repository at this point in the history
  • Loading branch information
darthdie committed May 15, 2018
2 parents e50c3ec + a1deb6b commit 3ea632e
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 117 deletions.
61 changes: 41 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ class MyApp {

## Usage

### Config

Starting with version 3.0.2, `CacheModule.forRoot()` optionally accepts a config object.

The config object currently accepts a `keyPrefix`, which is the the internal key prefix to use when storing items.

For backwards compatibility this defaults to `''`, but it's recommended to set this to a different value in order to prevent issues with `clearAll()`.

```ts
@NgModule({
...
imports: [
CacheModule.forRoot({ keyPrefix: 'my-app-cache' })
],
})
```

### Observables

#### Cache request
Expand All @@ -95,7 +112,7 @@ export class SomeProvider {
If you need to cache the whole response, for example if you need to access the Headers, you can pass in an object with the observe key set to 'response', i.e. `{ observe: 'response' }`. Then you can use `.pipe(map(res => res.body))` to extract the response body.
```js
```ts
...
let request = this.http.get(url, { observe: 'response' });
return this.cache.loadFromObservable(cacheKey, request).pipe(map(res => res.body));
Expand All @@ -107,7 +124,7 @@ return this.cache.loadFromObservable(cacheKey, request).pipe(map(res => res.body
`loadFromObservable` accepts an Observable and returns an Observable, so you are free to use all of the Observable operators.
For example error handling (on error, retry request every 6 seconds if fails):
```js
```ts
...
let request = this.http.get(url)
.pipe(retryWhen(error => error.timer(6000)));
Expand All @@ -122,7 +139,7 @@ return this.cache.loadFromObservable(cacheKey, request);
When you call this method and it will return the cached date (even if it's expired)
and immediately send a request to the server and then return the new data.
```js
```ts
...
let request = this.http.get(url);
let delayType = 'all'; // this indicates that it should send a new request to the server every time, you can also set it to 'none' which indicates that it should only send a new request when it's expired
Expand All @@ -143,7 +160,7 @@ and immediately send a request to the server and then return the new data.
#### Cache promises
```js
```ts
...
let key = 'some-promise';
let data = await this.cache.getOrSetItem(key, () => somePromiseFunction());
Expand All @@ -155,7 +172,7 @@ console.log("Saved data: ", data);
Similarly, you can use `getOrSetItem` or `getItem` with classic data.
```js
```ts
...
let key = 'heavily-calculated-function';

Expand All @@ -166,7 +183,7 @@ console.log('Saved data: ', data);
If you need more control in the event that the item is expired or doesn't exist, you can use the `getItem` method with error handling.
```js
```ts
...
let key = 'heavily-calculated-function';

Expand All @@ -186,7 +203,7 @@ console.log('Saved data: ', data);
You can also remove cached items by using the `removeItem` method.
```js
```ts
...
let key = 'some-promise';

Expand All @@ -198,7 +215,7 @@ this.cache.removeItem(key);
You can utilize the `removeItems` method to remove multiple items based on a wildcard pattern.
```js
```ts
...
await Promise.all([
service.saveItem('movies/comedy/1', 'Scott Pilgrim vs. The World'),
Expand All @@ -215,7 +232,7 @@ this.cache.removeItems('songs/metal/*');
If you need to check whether or not an item has been cached, ignoring whether or not it's expired, you can use the `itemExists` method.
```js
```ts
...
let key = 'some-promise';

Expand All @@ -227,16 +244,16 @@ let exists = await this.cache.itemExists(key); // returns either a boolean indic
If you ever need to get a cached item regardless of whether it's expired or not, you can use the `getRawItem` method.
```js
```ts
...
let key = 'some-promise';

let item = await this.cache.getRawItem(key);
...
```
There's also the `getRawItems` method, which returns a list of type `RawCacheItem` of all of the cached items.
```js
There's also the `getRawItems` method, which returns an array of the raw cached items.
```ts
...
let rawItems = await this.cache.getRawItems();
let firstItem = rawItems[0]; //Has the properties: key, value, expires, type, groupKey
Expand All @@ -250,7 +267,7 @@ let firstItem = rawItems[0]; //Has the properties: key, value, expires, type, gr
At times you may need to clear certain groups of cached items.
For example, if you have an infinite scroll list with a lot of items and the user triggers a pull to refresh, you may want to delete all of the cached list items. To do this, you can supply a group key as the 3rd parameter of `loadFromObservable`.
```js
```ts
...
loadList(pageNumber) {
let url = "http://google.com/?page=" + pageNumber;
Expand All @@ -265,7 +282,7 @@ loadList(pageNumber) {
Then when pull to refresh is triggered, you can use the `clearGroup` method and pass in your group key.
```js
```ts
...
pullToRefresh() {
this.cache.clearGroup("googleSearchPages");
Expand All @@ -277,7 +294,7 @@ pullToRefresh() {
If you want a custom TTL for a single request, you can pass it as the fourth parameter.
```js
```ts
let ttl = 60 * 60 * 24 * 7; // TTL in seconds for one week
let request = this.http.get(url);

Expand All @@ -286,36 +303,40 @@ return this.cache.loadFromObservable(cacheKey, request, groupKey, ttl);
#### Set default TTL
```js
```ts
this.cache.setDefaultTTL(60 * 60); //set the default cache TTL for 1 hour
```
#### Delete expired entries
It's automatically done on every startup, but you can do it manually.
```js
```ts
this.cache.clearExpired();
```
#### Delete all entries
```js
**!Important!**
Make sure that you have a `keyPrefix` set in the CacheModule config, otherwise this will clear everything in Ionic Storage.
```ts
this.cache.clearAll();
```
#### Disable cache
You can disable cache without any issues, it will pass all of the original Observables through and all Promises will be rejected.
```js
```ts
this.cache.enableCache(false);
```
#### Disable offline invalidation
You can also disable invalidating cached items when the device is offline.
```js
```ts
this.cache.setOfflineInvalidate(false);
```
94 changes: 94 additions & 0 deletions src/cache-storage.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage';

export interface StorageCacheItem {
key: string;
value: any;
expires: number;
type: string;
groupKey: string;
}

@Injectable()
export class CacheStorageService {
constructor(
private storage: Storage,
private keyPrefix: string
) {

}

public ready() {
return this.storage.ready();
}

public async set(key: string, value: any) {
await this.ready();

return this.storage.set(this.buildKey(key), value);
}

public async remove(key: string) {
await this.ready();

return this.storage.remove(this.buildKey(key));
}

public async get(key: string) {
await this.ready();

let value = await this.storage.get(this.buildKey(key));
return !!value ? Object.assign({ key: key }, value) : null;
}

public async exists(key: string) {
await this.ready();

return !!(await this.storage.get(this.buildKey(key)));
}

public async all(): Promise<StorageCacheItem[]> {
await this.ready();

let items: StorageCacheItem[] = [];
await this.storage.forEach((val: any, key: string) => {
if (this.isCachedItem(key, val)) {
items.push(Object.assign({ key: this.debuildKey(key) }, val));
}
});

return items;
}

/**
* @description Returns whether or not an object is a cached item.
* @return {boolean}
*/
private isCachedItem(key: string, item: any): boolean {
return item && item.expires && item.type && key.startsWith(this.keyPrefix);
}

/**
* Makes sure that the key is prefixed properly
* @return {string}
*/
private buildKey(key: string): string {
if (key.startsWith(this.keyPrefix)) {
return key;
}

return this.keyPrefix + key;
}

/**
* Makes sure that the key isn't prefixed
* @return {string}
*/
private debuildKey(key: string): string {
if (key.startsWith(this.keyPrefix)) {
return key.substr(this.keyPrefix.length);
}

return key;
}
}
16 changes: 12 additions & 4 deletions src/cache.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CacheService } from './cache.service';
import { IonicStorageModule } from '@ionic/storage';
import { CacheService, CacheConfig } from './cache.service';
import { IonicStorageModule, Storage } from '@ionic/storage';
import { CacheStorageService } from './cache-storage.service';

@NgModule({
imports: [
Expand All @@ -11,11 +12,18 @@ import { IonicStorageModule } from '@ionic/storage';
]
})
export class CacheModule {
static forRoot(): ModuleWithProviders {
static forRoot(cacheConfig?: CacheConfig): ModuleWithProviders {
cacheConfig = Object.assign({ keyPrefix: '' }, cacheConfig);
return {
ngModule: CacheModule,
providers: [
CacheService
{
provide: CacheService,
useFactory: (storage: Storage) => {
return new CacheService(new CacheStorageService(storage, cacheConfig.keyPrefix));
},
deps: [Storage]
}
]
};
}
Expand Down

0 comments on commit 3ea632e

Please sign in to comment.