Angular 5 caching http service api calls
In my Angular 5 app a certain dataset (not changing very often) is needed multiple times on different places in the app. After the API is called, the result is stored with the Observable do
operator. This way I implemented caching of HTTP requests within my service.
I'm using Angular 5.1.3 and RxJS 5.5.6.
Is this a good practise?
Are there better alternatives?
import Injectable from '@angular/core';
import HttpClient from '@angular/common/http';
import Observable from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';
@Injectable()
export class FruitService
fruits: Array<string> = ;
constructor(private http: HttpClient)
getFruits()
if (this.fruits.length === 0)
return this.http.get<any>('api/getFruits')
.do(data => this.fruits = data )
else
return Observable.of(this.fruits);
angular angular-httpclient
|
show 1 more comment
In my Angular 5 app a certain dataset (not changing very often) is needed multiple times on different places in the app. After the API is called, the result is stored with the Observable do
operator. This way I implemented caching of HTTP requests within my service.
I'm using Angular 5.1.3 and RxJS 5.5.6.
Is this a good practise?
Are there better alternatives?
import Injectable from '@angular/core';
import HttpClient from '@angular/common/http';
import Observable from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';
@Injectable()
export class FruitService
fruits: Array<string> = ;
constructor(private http: HttpClient)
getFruits()
if (this.fruits.length === 0)
return this.http.get<any>('api/getFruits')
.do(data => this.fruits = data )
else
return Observable.of(this.fruits);
angular angular-httpclient
1
Unrelated to your issue, but if you're using Angular 5, I'd recommend using pipeable operators for your observables instead of therxjs/add/operator
imports.
– Joe Clay
Apr 12 '18 at 13:36
Related question, stackoverflow.com/questions/40249629/…
– estus
Apr 12 '18 at 14:22
2
also keep in mind if you use pipe operator, do == tap ;)
– Ringo
Apr 12 '18 at 14:23
1
See the 'Why?' section of the page I linked for full info, but TL;DR: Pipeable operators are local to your file rather than applied globally to the entire app, they're more easily optimized out by build tools if they're not used, and they allow you to create custom operators much more easily.
– Joe Clay
Apr 12 '18 at 14:39
1
@JoeClay: You convinced me, thanks, I have learned something again
– Herman Fransen
Apr 13 '18 at 10:00
|
show 1 more comment
In my Angular 5 app a certain dataset (not changing very often) is needed multiple times on different places in the app. After the API is called, the result is stored with the Observable do
operator. This way I implemented caching of HTTP requests within my service.
I'm using Angular 5.1.3 and RxJS 5.5.6.
Is this a good practise?
Are there better alternatives?
import Injectable from '@angular/core';
import HttpClient from '@angular/common/http';
import Observable from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';
@Injectable()
export class FruitService
fruits: Array<string> = ;
constructor(private http: HttpClient)
getFruits()
if (this.fruits.length === 0)
return this.http.get<any>('api/getFruits')
.do(data => this.fruits = data )
else
return Observable.of(this.fruits);
angular angular-httpclient
In my Angular 5 app a certain dataset (not changing very often) is needed multiple times on different places in the app. After the API is called, the result is stored with the Observable do
operator. This way I implemented caching of HTTP requests within my service.
I'm using Angular 5.1.3 and RxJS 5.5.6.
Is this a good practise?
Are there better alternatives?
import Injectable from '@angular/core';
import HttpClient from '@angular/common/http';
import Observable from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';
@Injectable()
export class FruitService
fruits: Array<string> = ;
constructor(private http: HttpClient)
getFruits()
if (this.fruits.length === 0)
return this.http.get<any>('api/getFruits')
.do(data => this.fruits = data )
else
return Observable.of(this.fruits);
angular angular-httpclient
angular angular-httpclient
edited Apr 16 '18 at 12:54
Herman Fransen
asked Apr 12 '18 at 13:31
Herman FransenHerman Fransen
1,20421331
1,20421331
1
Unrelated to your issue, but if you're using Angular 5, I'd recommend using pipeable operators for your observables instead of therxjs/add/operator
imports.
– Joe Clay
Apr 12 '18 at 13:36
Related question, stackoverflow.com/questions/40249629/…
– estus
Apr 12 '18 at 14:22
2
also keep in mind if you use pipe operator, do == tap ;)
– Ringo
Apr 12 '18 at 14:23
1
See the 'Why?' section of the page I linked for full info, but TL;DR: Pipeable operators are local to your file rather than applied globally to the entire app, they're more easily optimized out by build tools if they're not used, and they allow you to create custom operators much more easily.
– Joe Clay
Apr 12 '18 at 14:39
1
@JoeClay: You convinced me, thanks, I have learned something again
– Herman Fransen
Apr 13 '18 at 10:00
|
show 1 more comment
1
Unrelated to your issue, but if you're using Angular 5, I'd recommend using pipeable operators for your observables instead of therxjs/add/operator
imports.
– Joe Clay
Apr 12 '18 at 13:36
Related question, stackoverflow.com/questions/40249629/…
– estus
Apr 12 '18 at 14:22
2
also keep in mind if you use pipe operator, do == tap ;)
– Ringo
Apr 12 '18 at 14:23
1
See the 'Why?' section of the page I linked for full info, but TL;DR: Pipeable operators are local to your file rather than applied globally to the entire app, they're more easily optimized out by build tools if they're not used, and they allow you to create custom operators much more easily.
– Joe Clay
Apr 12 '18 at 14:39
1
@JoeClay: You convinced me, thanks, I have learned something again
– Herman Fransen
Apr 13 '18 at 10:00
1
1
Unrelated to your issue, but if you're using Angular 5, I'd recommend using pipeable operators for your observables instead of the
rxjs/add/operator
imports.– Joe Clay
Apr 12 '18 at 13:36
Unrelated to your issue, but if you're using Angular 5, I'd recommend using pipeable operators for your observables instead of the
rxjs/add/operator
imports.– Joe Clay
Apr 12 '18 at 13:36
Related question, stackoverflow.com/questions/40249629/…
– estus
Apr 12 '18 at 14:22
Related question, stackoverflow.com/questions/40249629/…
– estus
Apr 12 '18 at 14:22
2
2
also keep in mind if you use pipe operator, do == tap ;)
– Ringo
Apr 12 '18 at 14:23
also keep in mind if you use pipe operator, do == tap ;)
– Ringo
Apr 12 '18 at 14:23
1
1
See the 'Why?' section of the page I linked for full info, but TL;DR: Pipeable operators are local to your file rather than applied globally to the entire app, they're more easily optimized out by build tools if they're not used, and they allow you to create custom operators much more easily.
– Joe Clay
Apr 12 '18 at 14:39
See the 'Why?' section of the page I linked for full info, but TL;DR: Pipeable operators are local to your file rather than applied globally to the entire app, they're more easily optimized out by build tools if they're not used, and they allow you to create custom operators much more easily.
– Joe Clay
Apr 12 '18 at 14:39
1
1
@JoeClay: You convinced me, thanks, I have learned something again
– Herman Fransen
Apr 13 '18 at 10:00
@JoeClay: You convinced me, thanks, I have learned something again
– Herman Fransen
Apr 13 '18 at 10:00
|
show 1 more comment
4 Answers
4
active
oldest
votes
The problem with your solution is that if a 2nd call comes while a 1st one is pending, it create a new http request. Here is how I would do it:
@Injectable()
export class FruitService
readonly fruits = this.http.get<any>('api/getFruits').shareReplay(1);
constructor(private http: HttpClient)
the bigger problem is when you have params and you want to cache based on the params. In that case you would need some sort of memoize
function like the one from lodash (https://lodash.com/docs/4.17.5#memoize)
You can also implement some in-memory cache
operator for the Observable
, like:
const cache = ;
function cacheOperator<T>(this: Observable<T>, key: string)
return new Observable<T>(observer =>
const cached = cache[key];
if (cached)
cached.subscribe(observer);
else
const add = this.multicast(new ReplaySubject(1));
cache[key] = add;
add.connect();
add.catch(err =>
delete cache[key];
throw err;
).subscribe(observer);
);
declare module 'rxjs/Observable'
interface Observable<T>
cache: typeof cacheOperator;
Observable.prototype.cache = cacheOperator;
and use it like:
getFruit(id: number)
return this.http.get<any>(`api/fruit/$id`).cache(`fruit:$id`);
2
cache operator is good POC but it isn't practical. Uncontrollable growth of private cache storage isn't a good thing IRL. There may be other concerns such as maximum capacity, expiration and force refreshing. This should be handled by cache service.
– estus
Apr 12 '18 at 14:21
1
Are you really sure this is working? I didn't get it to work for me.
– Herman Fransen
Apr 16 '18 at 12:30
1
@HermanFransen works fine for me: codepen.io/anon/pen/jzgLaW
– Andrei Tătar
Apr 16 '18 at 12:41
@Andrew : not the bigger problem, but the first one. It's not working in my case. I'm using Angular 5.1.3 and RxJS 5.5.6
– Herman Fransen
Apr 16 '18 at 12:50
1
@HermanFransen you mean shareReplay is not working? plnkr.co/edit/pwptj7lKR2FfRw5eVViJ?p=preview
– Andrei Tătar
Apr 16 '18 at 13:18
|
show 2 more comments
Actually, the easiest way of caching responses and also sharing a single subscription (not making a new request for every subscriber) is using publishReplay(1)
and refCount()
(I'm using pipable operators).
readonly fruits$ = this.http.get<any>('api/getFruits')
.pipe(
publishReplay(1), // publishReplay(1, _time_)
refCount(),
take(1),
);
Then when you want to get the cached/fresh value you'll just subscribe to fresh$
.
fresh$.subscribe(...)
The publishReplay
operator caches the value, then refCount
maintains only one subscription to its parent and unsubscribes if there are no subscribers. The take(1)
is necessary to properly complete the chain after a single value.
The most important part is that when you subscribe to this chain publishReplay
emits its buffer on subscription and if it contains a cached value it'll be immediately propagated to take(1)
that completes the chain so it won't create subscription to this.http.get
at all. If publishReplay
doesn't contain anything it'll subscribe to its source and make the HTTP request.
add a comment |
There is another way doing this with shareReplay and Angular 5, 6 or 7 : create a Service :
import Observable from 'rxjs/Observable';
import shareReplay from 'rxjs/operators';
const CACHE_SIZE = 1;
private cache$: Observable<Object>;
get api()
if ( !this.cache$ )
this.cache$ = this.requestApi().pipe( shareReplay(CACHE_SIZE) );
return this.cache_arbitrage$;
private requestApi()
const API_ENDPOINT = 'yoururl/';
return this.http.get<any>(API_ENDPOINT_ARBITRATION).pipe();
public resetCache()
this.cache$ = null;
To read the data directly in your html file use this :
<div *ngIf="this.apiService.api | async as api">api </div>
In your component you can subscribe like this:
this.apiService.api.pipe().subscribe(res => /*your code*/)
add a comment |
For Angular 6, RxJS 6 and simple cache expiration use the following code:
interface CacheEntry<T>
expiry: number;
observable: Observable<T>;
const DEFAULT_MAX_AGE = 300000;
const globalCache: [key: string]: CacheEntry<any>; = ;
export function cache(key: string, maxAge: number = DEFAULT_MAX_AGE)
return function cacheOperatorImpl<T>(source: Observable<T>)
return Observable.create(observer =>
const cached = globalCache[key];
if (cached && cached.expiry >= Date.now())
cached.observable.subscribe(observer);
else
const add = source.pipe(multicast(new ReplaySubject(1))) as ConnectableObservable<T>;
globalCache[key] = observable: add, expiry: Date.now() + maxAge;
add.connect();
add.pipe(
catchError(err =>
delete globalCache[key];
return throwError(err);
)
).subscribe(observer);
);
;
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f49797910%2fangular-5-caching-http-service-api-calls%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
The problem with your solution is that if a 2nd call comes while a 1st one is pending, it create a new http request. Here is how I would do it:
@Injectable()
export class FruitService
readonly fruits = this.http.get<any>('api/getFruits').shareReplay(1);
constructor(private http: HttpClient)
the bigger problem is when you have params and you want to cache based on the params. In that case you would need some sort of memoize
function like the one from lodash (https://lodash.com/docs/4.17.5#memoize)
You can also implement some in-memory cache
operator for the Observable
, like:
const cache = ;
function cacheOperator<T>(this: Observable<T>, key: string)
return new Observable<T>(observer =>
const cached = cache[key];
if (cached)
cached.subscribe(observer);
else
const add = this.multicast(new ReplaySubject(1));
cache[key] = add;
add.connect();
add.catch(err =>
delete cache[key];
throw err;
).subscribe(observer);
);
declare module 'rxjs/Observable'
interface Observable<T>
cache: typeof cacheOperator;
Observable.prototype.cache = cacheOperator;
and use it like:
getFruit(id: number)
return this.http.get<any>(`api/fruit/$id`).cache(`fruit:$id`);
2
cache operator is good POC but it isn't practical. Uncontrollable growth of private cache storage isn't a good thing IRL. There may be other concerns such as maximum capacity, expiration and force refreshing. This should be handled by cache service.
– estus
Apr 12 '18 at 14:21
1
Are you really sure this is working? I didn't get it to work for me.
– Herman Fransen
Apr 16 '18 at 12:30
1
@HermanFransen works fine for me: codepen.io/anon/pen/jzgLaW
– Andrei Tătar
Apr 16 '18 at 12:41
@Andrew : not the bigger problem, but the first one. It's not working in my case. I'm using Angular 5.1.3 and RxJS 5.5.6
– Herman Fransen
Apr 16 '18 at 12:50
1
@HermanFransen you mean shareReplay is not working? plnkr.co/edit/pwptj7lKR2FfRw5eVViJ?p=preview
– Andrei Tătar
Apr 16 '18 at 13:18
|
show 2 more comments
The problem with your solution is that if a 2nd call comes while a 1st one is pending, it create a new http request. Here is how I would do it:
@Injectable()
export class FruitService
readonly fruits = this.http.get<any>('api/getFruits').shareReplay(1);
constructor(private http: HttpClient)
the bigger problem is when you have params and you want to cache based on the params. In that case you would need some sort of memoize
function like the one from lodash (https://lodash.com/docs/4.17.5#memoize)
You can also implement some in-memory cache
operator for the Observable
, like:
const cache = ;
function cacheOperator<T>(this: Observable<T>, key: string)
return new Observable<T>(observer =>
const cached = cache[key];
if (cached)
cached.subscribe(observer);
else
const add = this.multicast(new ReplaySubject(1));
cache[key] = add;
add.connect();
add.catch(err =>
delete cache[key];
throw err;
).subscribe(observer);
);
declare module 'rxjs/Observable'
interface Observable<T>
cache: typeof cacheOperator;
Observable.prototype.cache = cacheOperator;
and use it like:
getFruit(id: number)
return this.http.get<any>(`api/fruit/$id`).cache(`fruit:$id`);
2
cache operator is good POC but it isn't practical. Uncontrollable growth of private cache storage isn't a good thing IRL. There may be other concerns such as maximum capacity, expiration and force refreshing. This should be handled by cache service.
– estus
Apr 12 '18 at 14:21
1
Are you really sure this is working? I didn't get it to work for me.
– Herman Fransen
Apr 16 '18 at 12:30
1
@HermanFransen works fine for me: codepen.io/anon/pen/jzgLaW
– Andrei Tătar
Apr 16 '18 at 12:41
@Andrew : not the bigger problem, but the first one. It's not working in my case. I'm using Angular 5.1.3 and RxJS 5.5.6
– Herman Fransen
Apr 16 '18 at 12:50
1
@HermanFransen you mean shareReplay is not working? plnkr.co/edit/pwptj7lKR2FfRw5eVViJ?p=preview
– Andrei Tătar
Apr 16 '18 at 13:18
|
show 2 more comments
The problem with your solution is that if a 2nd call comes while a 1st one is pending, it create a new http request. Here is how I would do it:
@Injectable()
export class FruitService
readonly fruits = this.http.get<any>('api/getFruits').shareReplay(1);
constructor(private http: HttpClient)
the bigger problem is when you have params and you want to cache based on the params. In that case you would need some sort of memoize
function like the one from lodash (https://lodash.com/docs/4.17.5#memoize)
You can also implement some in-memory cache
operator for the Observable
, like:
const cache = ;
function cacheOperator<T>(this: Observable<T>, key: string)
return new Observable<T>(observer =>
const cached = cache[key];
if (cached)
cached.subscribe(observer);
else
const add = this.multicast(new ReplaySubject(1));
cache[key] = add;
add.connect();
add.catch(err =>
delete cache[key];
throw err;
).subscribe(observer);
);
declare module 'rxjs/Observable'
interface Observable<T>
cache: typeof cacheOperator;
Observable.prototype.cache = cacheOperator;
and use it like:
getFruit(id: number)
return this.http.get<any>(`api/fruit/$id`).cache(`fruit:$id`);
The problem with your solution is that if a 2nd call comes while a 1st one is pending, it create a new http request. Here is how I would do it:
@Injectable()
export class FruitService
readonly fruits = this.http.get<any>('api/getFruits').shareReplay(1);
constructor(private http: HttpClient)
the bigger problem is when you have params and you want to cache based on the params. In that case you would need some sort of memoize
function like the one from lodash (https://lodash.com/docs/4.17.5#memoize)
You can also implement some in-memory cache
operator for the Observable
, like:
const cache = ;
function cacheOperator<T>(this: Observable<T>, key: string)
return new Observable<T>(observer =>
const cached = cache[key];
if (cached)
cached.subscribe(observer);
else
const add = this.multicast(new ReplaySubject(1));
cache[key] = add;
add.connect();
add.catch(err =>
delete cache[key];
throw err;
).subscribe(observer);
);
declare module 'rxjs/Observable'
interface Observable<T>
cache: typeof cacheOperator;
Observable.prototype.cache = cacheOperator;
and use it like:
getFruit(id: number)
return this.http.get<any>(`api/fruit/$id`).cache(`fruit:$id`);
answered Apr 12 '18 at 13:34
Andrei TătarAndrei Tătar
4,097722
4,097722
2
cache operator is good POC but it isn't practical. Uncontrollable growth of private cache storage isn't a good thing IRL. There may be other concerns such as maximum capacity, expiration and force refreshing. This should be handled by cache service.
– estus
Apr 12 '18 at 14:21
1
Are you really sure this is working? I didn't get it to work for me.
– Herman Fransen
Apr 16 '18 at 12:30
1
@HermanFransen works fine for me: codepen.io/anon/pen/jzgLaW
– Andrei Tătar
Apr 16 '18 at 12:41
@Andrew : not the bigger problem, but the first one. It's not working in my case. I'm using Angular 5.1.3 and RxJS 5.5.6
– Herman Fransen
Apr 16 '18 at 12:50
1
@HermanFransen you mean shareReplay is not working? plnkr.co/edit/pwptj7lKR2FfRw5eVViJ?p=preview
– Andrei Tătar
Apr 16 '18 at 13:18
|
show 2 more comments
2
cache operator is good POC but it isn't practical. Uncontrollable growth of private cache storage isn't a good thing IRL. There may be other concerns such as maximum capacity, expiration and force refreshing. This should be handled by cache service.
– estus
Apr 12 '18 at 14:21
1
Are you really sure this is working? I didn't get it to work for me.
– Herman Fransen
Apr 16 '18 at 12:30
1
@HermanFransen works fine for me: codepen.io/anon/pen/jzgLaW
– Andrei Tătar
Apr 16 '18 at 12:41
@Andrew : not the bigger problem, but the first one. It's not working in my case. I'm using Angular 5.1.3 and RxJS 5.5.6
– Herman Fransen
Apr 16 '18 at 12:50
1
@HermanFransen you mean shareReplay is not working? plnkr.co/edit/pwptj7lKR2FfRw5eVViJ?p=preview
– Andrei Tătar
Apr 16 '18 at 13:18
2
2
cache operator is good POC but it isn't practical. Uncontrollable growth of private cache storage isn't a good thing IRL. There may be other concerns such as maximum capacity, expiration and force refreshing. This should be handled by cache service.
– estus
Apr 12 '18 at 14:21
cache operator is good POC but it isn't practical. Uncontrollable growth of private cache storage isn't a good thing IRL. There may be other concerns such as maximum capacity, expiration and force refreshing. This should be handled by cache service.
– estus
Apr 12 '18 at 14:21
1
1
Are you really sure this is working? I didn't get it to work for me.
– Herman Fransen
Apr 16 '18 at 12:30
Are you really sure this is working? I didn't get it to work for me.
– Herman Fransen
Apr 16 '18 at 12:30
1
1
@HermanFransen works fine for me: codepen.io/anon/pen/jzgLaW
– Andrei Tătar
Apr 16 '18 at 12:41
@HermanFransen works fine for me: codepen.io/anon/pen/jzgLaW
– Andrei Tătar
Apr 16 '18 at 12:41
@Andrew : not the bigger problem, but the first one. It's not working in my case. I'm using Angular 5.1.3 and RxJS 5.5.6
– Herman Fransen
Apr 16 '18 at 12:50
@Andrew : not the bigger problem, but the first one. It's not working in my case. I'm using Angular 5.1.3 and RxJS 5.5.6
– Herman Fransen
Apr 16 '18 at 12:50
1
1
@HermanFransen you mean shareReplay is not working? plnkr.co/edit/pwptj7lKR2FfRw5eVViJ?p=preview
– Andrei Tătar
Apr 16 '18 at 13:18
@HermanFransen you mean shareReplay is not working? plnkr.co/edit/pwptj7lKR2FfRw5eVViJ?p=preview
– Andrei Tătar
Apr 16 '18 at 13:18
|
show 2 more comments
Actually, the easiest way of caching responses and also sharing a single subscription (not making a new request for every subscriber) is using publishReplay(1)
and refCount()
(I'm using pipable operators).
readonly fruits$ = this.http.get<any>('api/getFruits')
.pipe(
publishReplay(1), // publishReplay(1, _time_)
refCount(),
take(1),
);
Then when you want to get the cached/fresh value you'll just subscribe to fresh$
.
fresh$.subscribe(...)
The publishReplay
operator caches the value, then refCount
maintains only one subscription to its parent and unsubscribes if there are no subscribers. The take(1)
is necessary to properly complete the chain after a single value.
The most important part is that when you subscribe to this chain publishReplay
emits its buffer on subscription and if it contains a cached value it'll be immediately propagated to take(1)
that completes the chain so it won't create subscription to this.http.get
at all. If publishReplay
doesn't contain anything it'll subscribe to its source and make the HTTP request.
add a comment |
Actually, the easiest way of caching responses and also sharing a single subscription (not making a new request for every subscriber) is using publishReplay(1)
and refCount()
(I'm using pipable operators).
readonly fruits$ = this.http.get<any>('api/getFruits')
.pipe(
publishReplay(1), // publishReplay(1, _time_)
refCount(),
take(1),
);
Then when you want to get the cached/fresh value you'll just subscribe to fresh$
.
fresh$.subscribe(...)
The publishReplay
operator caches the value, then refCount
maintains only one subscription to its parent and unsubscribes if there are no subscribers. The take(1)
is necessary to properly complete the chain after a single value.
The most important part is that when you subscribe to this chain publishReplay
emits its buffer on subscription and if it contains a cached value it'll be immediately propagated to take(1)
that completes the chain so it won't create subscription to this.http.get
at all. If publishReplay
doesn't contain anything it'll subscribe to its source and make the HTTP request.
add a comment |
Actually, the easiest way of caching responses and also sharing a single subscription (not making a new request for every subscriber) is using publishReplay(1)
and refCount()
(I'm using pipable operators).
readonly fruits$ = this.http.get<any>('api/getFruits')
.pipe(
publishReplay(1), // publishReplay(1, _time_)
refCount(),
take(1),
);
Then when you want to get the cached/fresh value you'll just subscribe to fresh$
.
fresh$.subscribe(...)
The publishReplay
operator caches the value, then refCount
maintains only one subscription to its parent and unsubscribes if there are no subscribers. The take(1)
is necessary to properly complete the chain after a single value.
The most important part is that when you subscribe to this chain publishReplay
emits its buffer on subscription and if it contains a cached value it'll be immediately propagated to take(1)
that completes the chain so it won't create subscription to this.http.get
at all. If publishReplay
doesn't contain anything it'll subscribe to its source and make the HTTP request.
Actually, the easiest way of caching responses and also sharing a single subscription (not making a new request for every subscriber) is using publishReplay(1)
and refCount()
(I'm using pipable operators).
readonly fruits$ = this.http.get<any>('api/getFruits')
.pipe(
publishReplay(1), // publishReplay(1, _time_)
refCount(),
take(1),
);
Then when you want to get the cached/fresh value you'll just subscribe to fresh$
.
fresh$.subscribe(...)
The publishReplay
operator caches the value, then refCount
maintains only one subscription to its parent and unsubscribes if there are no subscribers. The take(1)
is necessary to properly complete the chain after a single value.
The most important part is that when you subscribe to this chain publishReplay
emits its buffer on subscription and if it contains a cached value it'll be immediately propagated to take(1)
that completes the chain so it won't create subscription to this.http.get
at all. If publishReplay
doesn't contain anything it'll subscribe to its source and make the HTTP request.
answered Apr 12 '18 at 14:48
martinmartin
46k1193137
46k1193137
add a comment |
add a comment |
There is another way doing this with shareReplay and Angular 5, 6 or 7 : create a Service :
import Observable from 'rxjs/Observable';
import shareReplay from 'rxjs/operators';
const CACHE_SIZE = 1;
private cache$: Observable<Object>;
get api()
if ( !this.cache$ )
this.cache$ = this.requestApi().pipe( shareReplay(CACHE_SIZE) );
return this.cache_arbitrage$;
private requestApi()
const API_ENDPOINT = 'yoururl/';
return this.http.get<any>(API_ENDPOINT_ARBITRATION).pipe();
public resetCache()
this.cache$ = null;
To read the data directly in your html file use this :
<div *ngIf="this.apiService.api | async as api">api </div>
In your component you can subscribe like this:
this.apiService.api.pipe().subscribe(res => /*your code*/)
add a comment |
There is another way doing this with shareReplay and Angular 5, 6 or 7 : create a Service :
import Observable from 'rxjs/Observable';
import shareReplay from 'rxjs/operators';
const CACHE_SIZE = 1;
private cache$: Observable<Object>;
get api()
if ( !this.cache$ )
this.cache$ = this.requestApi().pipe( shareReplay(CACHE_SIZE) );
return this.cache_arbitrage$;
private requestApi()
const API_ENDPOINT = 'yoururl/';
return this.http.get<any>(API_ENDPOINT_ARBITRATION).pipe();
public resetCache()
this.cache$ = null;
To read the data directly in your html file use this :
<div *ngIf="this.apiService.api | async as api">api </div>
In your component you can subscribe like this:
this.apiService.api.pipe().subscribe(res => /*your code*/)
add a comment |
There is another way doing this with shareReplay and Angular 5, 6 or 7 : create a Service :
import Observable from 'rxjs/Observable';
import shareReplay from 'rxjs/operators';
const CACHE_SIZE = 1;
private cache$: Observable<Object>;
get api()
if ( !this.cache$ )
this.cache$ = this.requestApi().pipe( shareReplay(CACHE_SIZE) );
return this.cache_arbitrage$;
private requestApi()
const API_ENDPOINT = 'yoururl/';
return this.http.get<any>(API_ENDPOINT_ARBITRATION).pipe();
public resetCache()
this.cache$ = null;
To read the data directly in your html file use this :
<div *ngIf="this.apiService.api | async as api">api </div>
In your component you can subscribe like this:
this.apiService.api.pipe().subscribe(res => /*your code*/)
There is another way doing this with shareReplay and Angular 5, 6 or 7 : create a Service :
import Observable from 'rxjs/Observable';
import shareReplay from 'rxjs/operators';
const CACHE_SIZE = 1;
private cache$: Observable<Object>;
get api()
if ( !this.cache$ )
this.cache$ = this.requestApi().pipe( shareReplay(CACHE_SIZE) );
return this.cache_arbitrage$;
private requestApi()
const API_ENDPOINT = 'yoururl/';
return this.http.get<any>(API_ENDPOINT_ARBITRATION).pipe();
public resetCache()
this.cache$ = null;
To read the data directly in your html file use this :
<div *ngIf="this.apiService.api | async as api">api </div>
In your component you can subscribe like this:
this.apiService.api.pipe().subscribe(res => /*your code*/)
answered Dec 19 '18 at 15:59
D3FD3F
212
212
add a comment |
add a comment |
For Angular 6, RxJS 6 and simple cache expiration use the following code:
interface CacheEntry<T>
expiry: number;
observable: Observable<T>;
const DEFAULT_MAX_AGE = 300000;
const globalCache: [key: string]: CacheEntry<any>; = ;
export function cache(key: string, maxAge: number = DEFAULT_MAX_AGE)
return function cacheOperatorImpl<T>(source: Observable<T>)
return Observable.create(observer =>
const cached = globalCache[key];
if (cached && cached.expiry >= Date.now())
cached.observable.subscribe(observer);
else
const add = source.pipe(multicast(new ReplaySubject(1))) as ConnectableObservable<T>;
globalCache[key] = observable: add, expiry: Date.now() + maxAge;
add.connect();
add.pipe(
catchError(err =>
delete globalCache[key];
return throwError(err);
)
).subscribe(observer);
);
;
add a comment |
For Angular 6, RxJS 6 and simple cache expiration use the following code:
interface CacheEntry<T>
expiry: number;
observable: Observable<T>;
const DEFAULT_MAX_AGE = 300000;
const globalCache: [key: string]: CacheEntry<any>; = ;
export function cache(key: string, maxAge: number = DEFAULT_MAX_AGE)
return function cacheOperatorImpl<T>(source: Observable<T>)
return Observable.create(observer =>
const cached = globalCache[key];
if (cached && cached.expiry >= Date.now())
cached.observable.subscribe(observer);
else
const add = source.pipe(multicast(new ReplaySubject(1))) as ConnectableObservable<T>;
globalCache[key] = observable: add, expiry: Date.now() + maxAge;
add.connect();
add.pipe(
catchError(err =>
delete globalCache[key];
return throwError(err);
)
).subscribe(observer);
);
;
add a comment |
For Angular 6, RxJS 6 and simple cache expiration use the following code:
interface CacheEntry<T>
expiry: number;
observable: Observable<T>;
const DEFAULT_MAX_AGE = 300000;
const globalCache: [key: string]: CacheEntry<any>; = ;
export function cache(key: string, maxAge: number = DEFAULT_MAX_AGE)
return function cacheOperatorImpl<T>(source: Observable<T>)
return Observable.create(observer =>
const cached = globalCache[key];
if (cached && cached.expiry >= Date.now())
cached.observable.subscribe(observer);
else
const add = source.pipe(multicast(new ReplaySubject(1))) as ConnectableObservable<T>;
globalCache[key] = observable: add, expiry: Date.now() + maxAge;
add.connect();
add.pipe(
catchError(err =>
delete globalCache[key];
return throwError(err);
)
).subscribe(observer);
);
;
For Angular 6, RxJS 6 and simple cache expiration use the following code:
interface CacheEntry<T>
expiry: number;
observable: Observable<T>;
const DEFAULT_MAX_AGE = 300000;
const globalCache: [key: string]: CacheEntry<any>; = ;
export function cache(key: string, maxAge: number = DEFAULT_MAX_AGE)
return function cacheOperatorImpl<T>(source: Observable<T>)
return Observable.create(observer =>
const cached = globalCache[key];
if (cached && cached.expiry >= Date.now())
cached.observable.subscribe(observer);
else
const add = source.pipe(multicast(new ReplaySubject(1))) as ConnectableObservable<T>;
globalCache[key] = observable: add, expiry: Date.now() + maxAge;
add.connect();
add.pipe(
catchError(err =>
delete globalCache[key];
return throwError(err);
)
).subscribe(observer);
);
;
answered Nov 14 '18 at 20:27
mohlendomohlendo
355919
355919
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f49797910%2fangular-5-caching-http-service-api-calls%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
Unrelated to your issue, but if you're using Angular 5, I'd recommend using pipeable operators for your observables instead of the
rxjs/add/operator
imports.– Joe Clay
Apr 12 '18 at 13:36
Related question, stackoverflow.com/questions/40249629/…
– estus
Apr 12 '18 at 14:22
2
also keep in mind if you use pipe operator, do == tap ;)
– Ringo
Apr 12 '18 at 14:23
1
See the 'Why?' section of the page I linked for full info, but TL;DR: Pipeable operators are local to your file rather than applied globally to the entire app, they're more easily optimized out by build tools if they're not used, and they allow you to create custom operators much more easily.
– Joe Clay
Apr 12 '18 at 14:39
1
@JoeClay: You convinced me, thanks, I have learned something again
– Herman Fransen
Apr 13 '18 at 10:00