Skip to content

Instantly share code, notes, and snippets.

@ckimrie
Last active December 12, 2023 20:53
Show Gist options
  • Save ckimrie/63334b6ad2873bd9db7ccbbf8ccdfd53 to your computer and use it in GitHub Desktop.
Save ckimrie/63334b6ad2873bd9db7ccbbf8ccdfd53 to your computer and use it in GitHub Desktop.
Example on how to achieve RxJS observable caching and storage in Angular 2+. Ideal for storing Http requests client side for offline usage.
import { Component, OnInit, OnDestroy } from '@angular/core';
import {Http} from "@angular/http";
import { LocalCacheService } from "./local-cache.service";
@Component({
selector: 'app-example',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class ExampleComponent implements OnInit, OnDestroy {
constructor(public http:Http, public cache:LocalCacheService){
//Cache an observable
let requestObservable = this.http.get("http://example.com/path/to/api").map(res => res.json())
this.cache.observable('my-cache-key', requestObservable, 300).subscribe(result => {
//Use result
console.log(result)
});
}
}
import {Injectable} from "@angular/core";
import {LocalStorageService} from "./local-storage.service";
import {Observable} from "rxjs/Observable";
import {isEmpty, isString, isNumber, isDate} from 'lodash';
@Injectable()
export class LocalCacheService {
/**
* Default expiry in seconds
*
* @type {number}
*/
defaultExpires: number = 86400; //24Hrs
constructor(private localstorage: LocalStorageService) {}
/**
* Cache or use result from observable
*
* If cache key does not exist or is expired, observable supplied in argument is returned and result cached
*
* @param key
* @param observable
* @param expires
* @returns {Observable<T>}
*/
public observable<T>(key: string, observable: Observable<T>, expires:number = this.defaultExpires): Observable<T> {
//First fetch the item from localstorage (even though it may not exist)
return this.localstorage.getItem(key)
//If the cached value has expired, nullify it, otherwise pass it through
.map((val: CacheStorageRecord) => {
if(val){
return (new Date(val.expires)).getTime() > Date.now() ? val : null;
}
return null;
})
//At this point, if we encounter a null value, either it doesnt exist in the cache or it has expired.
//If it doesnt exist, simply return the observable that has been passed in, caching its value as it passes through
.flatMap((val: CacheStorageRecord | null) => {
if (!isEmpty(val)) {
return Observable.of(val.value);
} else {
return observable.flatMap((val:any) => this.value(key, val, expires)); //The result may have 'expires' explicitly set
}
})
}
/**
* Cache supplied value until expiry
*
* @param key
* @param value
* @param expires
* @returns {Observable<T>}
*/
value<T>(key:string, value:T, expires:number|string|Date = this.defaultExpires):Observable<T>{
let _expires:Date = this.sanitizeAndGenerateDateExpiry(expires);
return this.localstorage.setItem(key, {
expires: _expires,
value: value
}).map(val => val.value);
}
/**
*
* @param key
* @returns {Observable<null>}
*/
expire(key:string):Observable<null>{
return this.localstorage.removeItem(key);
}
/**
*
* @param expires
* @returns {Date}
*/
private sanitizeAndGenerateDateExpiry(expires:string|number|Date):Date{
let expiryDate:Date = this.expiryToDate(expires);
//Dont allow expiry dates in the past
if(expiryDate.getTime() <= Date.now()){
return new Date(Date.now() + this.defaultExpires);
}
return expiryDate;
}
/**
*
* @param expires
* @returns {Date}
*/
private expiryToDate(expires:number|string|Date):Date{
if(isNumber(expires)){
return new Date(Date.now() + Math.abs(expires)*1000);
}
if(isString(expires)){
return new Date(expires);
}
if(isDate(expires)){
return expires;
}
return new Date();
}
}
/**
* Cache storage record interface
*/
interface CacheStorageRecord {
expires: Date,
value: any
}
import * as localforage from 'localforage';
import {Injectable} from "@angular/core";
import {Observable} from "rxjs/Observable";
@Injectable()
export class LocalStorageService {
/**
*
* @param key
* @param value
* @returns {any}
*/
public setItem<T>(key:string, value:T):Observable<T>{
return Observable.fromPromise(localforage.setItem(key, value))
}
/**
*
* @param key
* @returns {any}
*/
public getItem<T>(key:string):Observable<T>{
return Observable.fromPromise(localforage.getItem(key))
}
/**
*
* @param key
* @returns {any}
*/
public removeItem(key:string):Observable<void>{
return Observable.fromPromise(localforage.removeItem(key))
}
}
@alexandis
Copy link

Great!

@TaHaElyasi
Copy link

@atdetquizan Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment