What is the correct way to share the result of an Angular Http network call in RxJs 5?
By using Http, we call a method that does a network call and returns an http observable:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
If we take this observable and add multiple subscribers to it:
let network$ = getCustomer();
let subscriber1 = network$.subscribe(...);
let subscriber2 = network$.subscribe(...);
What we want to do, is ensure that this does not cause multiple network requests.
This might seem like an unusual scenario, but its actually quite common: for example if the caller subscribes to the observable to display an error message, and passes it to the template using the async pipe, we already have two subscribers.
What is the correct way of doing that in RxJs 5?
Namely, this seems to work fine:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json()).share();
}
But is this the idiomatic way of doing this in RxJs 5, or should we do something else instead?
Note : As per Angular 5 new HttpClient
, the .map(res => res.json())
part in all examples is now useless, as JSON result is now assumed by default.
angular rxjs angular2-services rxjs5
add a comment |
By using Http, we call a method that does a network call and returns an http observable:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
If we take this observable and add multiple subscribers to it:
let network$ = getCustomer();
let subscriber1 = network$.subscribe(...);
let subscriber2 = network$.subscribe(...);
What we want to do, is ensure that this does not cause multiple network requests.
This might seem like an unusual scenario, but its actually quite common: for example if the caller subscribes to the observable to display an error message, and passes it to the template using the async pipe, we already have two subscribers.
What is the correct way of doing that in RxJs 5?
Namely, this seems to work fine:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json()).share();
}
But is this the idiomatic way of doing this in RxJs 5, or should we do something else instead?
Note : As per Angular 5 new HttpClient
, the .map(res => res.json())
part in all examples is now useless, as JSON result is now assumed by default.
angular rxjs angular2-services rxjs5
1
> share is identical to publish().refCount(). Actually it's not. See the following discussion: github.com/ReactiveX/rxjs/issues/1363
– Christian
Mar 29 '16 at 17:25
1
edited question, according to the issue looks like the docs on the code need to be updated -> github.com/ReactiveX/rxjs/blob/master/src/operator/share.ts
– Angular University
Mar 29 '16 at 17:49
I think 'it depends'. But for calls where you can't cache the data locally b/c it might not make sense due to parameters changing/combinations .share() seems to absolutely be the right thing. But if you can cache things locally some of the other answers regarding ReplaySubject/BehaviorSubject are also good solutions.
– JimB
Apr 25 '16 at 12:59
I think not only we need cache the data, we also need update/modify the data cached. It's a common case. For example, if I want to add a new field to the model cached or update the value of field. Maybe create a singleton DataCacheService with CRUD method is a better way? Like store of Redux. What do you think?
– slideshowp2
Sep 26 '17 at 3:26
You could simply use ngx-cacheable! It better suits your scenario. Refer my answer below
– Tushar Walzade
Nov 19 '18 at 14:07
add a comment |
By using Http, we call a method that does a network call and returns an http observable:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
If we take this observable and add multiple subscribers to it:
let network$ = getCustomer();
let subscriber1 = network$.subscribe(...);
let subscriber2 = network$.subscribe(...);
What we want to do, is ensure that this does not cause multiple network requests.
This might seem like an unusual scenario, but its actually quite common: for example if the caller subscribes to the observable to display an error message, and passes it to the template using the async pipe, we already have two subscribers.
What is the correct way of doing that in RxJs 5?
Namely, this seems to work fine:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json()).share();
}
But is this the idiomatic way of doing this in RxJs 5, or should we do something else instead?
Note : As per Angular 5 new HttpClient
, the .map(res => res.json())
part in all examples is now useless, as JSON result is now assumed by default.
angular rxjs angular2-services rxjs5
By using Http, we call a method that does a network call and returns an http observable:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
If we take this observable and add multiple subscribers to it:
let network$ = getCustomer();
let subscriber1 = network$.subscribe(...);
let subscriber2 = network$.subscribe(...);
What we want to do, is ensure that this does not cause multiple network requests.
This might seem like an unusual scenario, but its actually quite common: for example if the caller subscribes to the observable to display an error message, and passes it to the template using the async pipe, we already have two subscribers.
What is the correct way of doing that in RxJs 5?
Namely, this seems to work fine:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json()).share();
}
But is this the idiomatic way of doing this in RxJs 5, or should we do something else instead?
Note : As per Angular 5 new HttpClient
, the .map(res => res.json())
part in all examples is now useless, as JSON result is now assumed by default.
angular rxjs angular2-services rxjs5
angular rxjs angular2-services rxjs5
edited Jan 22 '18 at 7:58
Pac0
7,49722544
7,49722544
asked Mar 28 '16 at 21:55


Angular University
29.9k125576
29.9k125576
1
> share is identical to publish().refCount(). Actually it's not. See the following discussion: github.com/ReactiveX/rxjs/issues/1363
– Christian
Mar 29 '16 at 17:25
1
edited question, according to the issue looks like the docs on the code need to be updated -> github.com/ReactiveX/rxjs/blob/master/src/operator/share.ts
– Angular University
Mar 29 '16 at 17:49
I think 'it depends'. But for calls where you can't cache the data locally b/c it might not make sense due to parameters changing/combinations .share() seems to absolutely be the right thing. But if you can cache things locally some of the other answers regarding ReplaySubject/BehaviorSubject are also good solutions.
– JimB
Apr 25 '16 at 12:59
I think not only we need cache the data, we also need update/modify the data cached. It's a common case. For example, if I want to add a new field to the model cached or update the value of field. Maybe create a singleton DataCacheService with CRUD method is a better way? Like store of Redux. What do you think?
– slideshowp2
Sep 26 '17 at 3:26
You could simply use ngx-cacheable! It better suits your scenario. Refer my answer below
– Tushar Walzade
Nov 19 '18 at 14:07
add a comment |
1
> share is identical to publish().refCount(). Actually it's not. See the following discussion: github.com/ReactiveX/rxjs/issues/1363
– Christian
Mar 29 '16 at 17:25
1
edited question, according to the issue looks like the docs on the code need to be updated -> github.com/ReactiveX/rxjs/blob/master/src/operator/share.ts
– Angular University
Mar 29 '16 at 17:49
I think 'it depends'. But for calls where you can't cache the data locally b/c it might not make sense due to parameters changing/combinations .share() seems to absolutely be the right thing. But if you can cache things locally some of the other answers regarding ReplaySubject/BehaviorSubject are also good solutions.
– JimB
Apr 25 '16 at 12:59
I think not only we need cache the data, we also need update/modify the data cached. It's a common case. For example, if I want to add a new field to the model cached or update the value of field. Maybe create a singleton DataCacheService with CRUD method is a better way? Like store of Redux. What do you think?
– slideshowp2
Sep 26 '17 at 3:26
You could simply use ngx-cacheable! It better suits your scenario. Refer my answer below
– Tushar Walzade
Nov 19 '18 at 14:07
1
1
> share is identical to publish().refCount(). Actually it's not. See the following discussion: github.com/ReactiveX/rxjs/issues/1363
– Christian
Mar 29 '16 at 17:25
> share is identical to publish().refCount(). Actually it's not. See the following discussion: github.com/ReactiveX/rxjs/issues/1363
– Christian
Mar 29 '16 at 17:25
1
1
edited question, according to the issue looks like the docs on the code need to be updated -> github.com/ReactiveX/rxjs/blob/master/src/operator/share.ts
– Angular University
Mar 29 '16 at 17:49
edited question, according to the issue looks like the docs on the code need to be updated -> github.com/ReactiveX/rxjs/blob/master/src/operator/share.ts
– Angular University
Mar 29 '16 at 17:49
I think 'it depends'. But for calls where you can't cache the data locally b/c it might not make sense due to parameters changing/combinations .share() seems to absolutely be the right thing. But if you can cache things locally some of the other answers regarding ReplaySubject/BehaviorSubject are also good solutions.
– JimB
Apr 25 '16 at 12:59
I think 'it depends'. But for calls where you can't cache the data locally b/c it might not make sense due to parameters changing/combinations .share() seems to absolutely be the right thing. But if you can cache things locally some of the other answers regarding ReplaySubject/BehaviorSubject are also good solutions.
– JimB
Apr 25 '16 at 12:59
I think not only we need cache the data, we also need update/modify the data cached. It's a common case. For example, if I want to add a new field to the model cached or update the value of field. Maybe create a singleton DataCacheService with CRUD method is a better way? Like store of Redux. What do you think?
– slideshowp2
Sep 26 '17 at 3:26
I think not only we need cache the data, we also need update/modify the data cached. It's a common case. For example, if I want to add a new field to the model cached or update the value of field. Maybe create a singleton DataCacheService with CRUD method is a better way? Like store of Redux. What do you think?
– slideshowp2
Sep 26 '17 at 3:26
You could simply use ngx-cacheable! It better suits your scenario. Refer my answer below
– Tushar Walzade
Nov 19 '18 at 14:07
You could simply use ngx-cacheable! It better suits your scenario. Refer my answer below
– Tushar Walzade
Nov 19 '18 at 14:07
add a comment |
21 Answers
21
active
oldest
votes
Cache the data and if available cached, return this otherwise make the HTTP request.
import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of'; //proper way to import the 'of' operator
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/map';
import {Data} from './data';
@Injectable()
export class DataService {
private url:string = 'https://cors-test.appspot.com/test';
private data: Data;
private observable: Observable<any>;
constructor(private http:Http) {}
getData() {
if(this.data) {
// if `data` is available just return it as `Observable`
return Observable.of(this.data);
} else if(this.observable) {
// if `this.observable` is set then the request is in progress
// return the `Observable` for the ongoing request
return this.observable;
} else {
// example header (not necessary)
let headers = new Headers();
headers.append('Content-Type', 'application/json');
// create the request, store the `Observable` for subsequent subscribers
this.observable = this.http.get(this.url, {
headers: headers
})
.map(response => {
// when the cached data is available we don't need the `Observable` reference anymore
this.observable = null;
if(response.status == 400) {
return "FAILURE";
} else if(response.status == 200) {
this.data = new Data(response.json());
return this.data;
}
// make it shared so more than one subscriber can get the result
})
.share();
return this.observable;
}
}
}
Plunker example
This artile https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html is a great explanation how to cache with shareReplay
.
3
do()
in contrary tomap()
doesn't modify the event. You could usemap()
as well but then you had to ensure the correct value is returned at the end of the callback.
– Günter Zöchbauer
Apr 15 '16 at 16:00
3
If the call-site that does the.subscribe()
doesn't need the value you can do that because it might get justnull
(depending on whatthis.extractData
returns), but IMHO this doesn't express the intent of the code well.
– Günter Zöchbauer
Apr 15 '16 at 16:08
2
Whenthis.extraData
ends likeextraData() { if(foo) { doSomething();}}
otherwise the result of the last expression is returned which might not be what you want.
– Günter Zöchbauer
Apr 15 '16 at 19:15
7
@Günter, thank you for the code, it works. However, I am trying to understand why you are keeping track of Data and Observable separately. Wouldn't you effectively achieve the same effect by caching just Observable<Data> like this?if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
– July.Tech
Dec 15 '16 at 17:12
3
@HarleenKaur It's a class the received JSON is deserialized to, to get strong type checking and autocompletion. There is no need to use it, but it's common.
– Günter Zöchbauer
May 31 '17 at 17:22
|
show 15 more comments
Per @Cristian suggestion, this is one way that works well for HTTP observables, that only emit once and then they complete:
getCustomer() {
return this.http.get('/someUrl')
.map(res => res.json()).publishLast().refCount();
}
There are a couple of problems with using this approach - the returned observable cannot be cancelled or retried. This might not be an issue for you, but then again it might. If this is a problem then theshare
operator might be a reasonable choice (albeit with some nasty edge cases). For a deep dive discussion on the options see comments section in this blog post: blog.jhades.org/…
– Christian
Apr 3 '16 at 8:46
1
Small clarification... Although strictly the source observable being shared bypublishLast().refCount()
cannot be cancelled, once all subscriptions to the observable returned byrefCount
have been cancelled, the net effect is the source observable will be unsubscribed, cancelling it if it where "inflight"
– Christian
Apr 4 '16 at 6:43
@Christian Hey, can you explain what you mean by saying "cannot be cancelled or retried"? Thanks.
– undefined
Jun 7 '17 at 5:19
add a comment |
UPDATE: Ben Lesh says the next minor release after 5.2.0, you'll be able to just call shareReplay() to truly cache.
PREVIOUSLY.....
Firstly, don't use share() or publishReplay(1).refCount(), they are the same and the problem with it, is that it only shares if connections are made while the observable is active, if you connect after it completes, it creates a new observable again, translation, not really caching.
Birowski gave the right solution above, which is to use ReplaySubject. ReplaySubject will caches the values you give it (bufferSize) in our case 1. It will not create a new observable like share() once refCount reaches zero and you make a new connection, which is the right behavior for caching.
Here's a reusable function
export function cacheable<T>(o: Observable<T>): Observable<T> {
let replay = new ReplaySubject<T>(1);
o.subscribe(
x => replay.next(x),
x => replay.error(x),
() => replay.complete()
);
return replay.asObservable();
}
Here's how to use it
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { cacheable } from '../utils/rxjs-functions';
@Injectable()
export class SettingsService {
_cache: Observable<any>;
constructor(private _http: Http, ) { }
refresh = () => {
if (this._cache) {
return this._cache;
}
return this._cache = cacheable<any>(this._http.get('YOUR URL'));
}
}
Below is a more advance version of the cacheable function This one allows has its own lookup table + the ability to provide a custom lookup table. This way, you don't have to check this._cache like in the above example. Also notice that instead of passing the observable as the first argument, you pass a function which returns the observables, this is because Angular's Http executes right away, so by returning a lazy executed function, we can decide not to call it if it's already in our cache.
let cacheableCache: { [key: string]: Observable<any> } = {};
export function cacheable<T>(returnObservable: () => Observable<T>, key?: string, customCache?: { [key: string]: Observable<T> }): Observable<T> {
if (!!key && (customCache || cacheableCache)[key]) {
return (customCache || cacheableCache)[key] as Observable<T>;
}
let replay = new ReplaySubject<T>(1);
returnObservable().subscribe(
x => replay.next(x),
x => replay.error(x),
() => replay.complete()
);
let observable = replay.asObservable();
if (!!key) {
if (!!customCache) {
customCache[key] = observable;
} else {
cacheableCache[key] = observable;
}
}
return observable;
}
Usage:
getData() => cacheable(this._http.get("YOUR URL"), "this is key for my cache")
add a comment |
according to this article
It turns out we can easily add caching to the observable by adding publishReplay(1) and refCount.
so inside if statements just append
.publishReplay(1)
.refCount();
to .map(...)
add a comment |
rxjs 5.4.0 has a new shareReplay method.
- rx-book shareReplay()
- No docs at reactivex.io/rxjs
The author explicitly says "ideal for handling things like caching AJAX results"
rxjs PR #2443 feat(shareReplay): adds shareReplay
variant of publishReplay
shareReplay returns an observable that is the source multicasted over
a ReplaySubject. That replay subject is recycled on error from the
source, but not on completion of the source. This makes shareReplay
ideal for handling things like caching AJAX results, as it's
retryable. It's repeat behavior, however, differs from share in that
it will not repeat the source observable, rather it will repeat the
source observable's values.
Is it related to this? These docs are from 2014 though. github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/…
– Aaron Hoffman
Jun 21 '17 at 14:29
4
I tried adding .shareReplay(1, 10000) to an observable but I didn't notice any caching or behavior change. Is there a working example available?
– Aydus-Matthew
Sep 13 '17 at 1:44
Looking at the changelog github.com/ReactiveX/rxjs/blob/… It appeared earlier, was removed in v5, added back in 5.4 - that rx-book link does refer to v4, but it exists in the current LTS v5.5.6 and it's in v6. I imagine the rx-book link there is out of date.
– Jason Awbrey
Feb 4 '18 at 22:54
add a comment |
I starred the question, but i'll try and have a go at this.
//this will be the shared observable that
//anyone can subscribe to, get the value,
//but not cause an api request
let customer$ = new Rx.ReplaySubject(1);
getCustomer().subscribe(customer$);
//here's the first subscriber
customer$.subscribe(val => console.log('subscriber 1: ' + val));
//here's the second subscriber
setTimeout(() => {
customer$.subscribe(val => console.log('subscriber 2: ' + val));
}, 1000);
function getCustomer() {
return new Rx.Observable(observer => {
console.log('api request');
setTimeout(() => {
console.log('api response');
observer.next('customer object');
observer.complete();
}, 500);
});
}
Here's the proof :)
There is but one takeaway: getCustomer().subscribe(customer$)
We are not subscribing to the api response of getCustomer()
, we are subscribing to a ReplaySubject which is observable which is also able to subscribe to a different Observable and (and this is important) hold it's last emitted value and republish it to any of it's(ReplaySubject's) subscribers.
1
I like this approach as it makes good use of rxjs and no need to add custom logic, thank-you
– Thibs
Jan 25 '17 at 1:47
add a comment |
The implementation you choose is going to depend on if you want unsubscribe() to cancel your HTTP request or not.
In any case, TypeScript decorators are a nice way of standardizing behavior. This is the one I wrote:
@CacheObservableArgsKey
getMyThing(id: string): Observable<any> {
return this.http.get('things/'+id);
}
Decorator definition:
/**
* Decorator that replays and connects to the Observable returned from the function.
* Caches the result using all arguments to form a key.
* @param target
* @param name
* @param descriptor
* @returns {PropertyDescriptor}
*/
export function CacheObservableArgsKey(target: Object, name: string, descriptor: PropertyDescriptor) {
const originalFunc = descriptor.value;
const cacheMap = new Map<string, any>();
descriptor.value = function(this: any, ...args: any): any {
const key = args.join('::');
let returnValue = cacheMap.get(key);
if (returnValue !== undefined) {
console.log(`${name} cache-hit ${key}`, returnValue);
return returnValue;
}
returnValue = originalFunc.apply(this, args);
console.log(`${name} cache-miss ${key} new`, returnValue);
if (returnValue instanceof Observable) {
returnValue = returnValue.publishReplay(1);
returnValue.connect();
}
else {
console.warn('CacheHttpArgsKey: value not an Observable cannot publishReplay and connect', returnValue);
}
cacheMap.set(key, returnValue);
return returnValue;
};
return descriptor;
}
Hi @Arlo - the example above does not compile.Property 'connect' does not exist on type '{}'.
from the linereturnValue.connect();
. Can you elaborate?
– Hoof
Nov 15 '17 at 7:49
add a comment |
I found a way to store the http get result into sessionStorage and use it for the session, so that it will never call the server again.
I used it to call github API to avoid usage limit.
@Injectable()
export class HttpCache {
constructor(private http: Http) {}
get(url: string): Observable<any> {
let cached: any;
if (cached === sessionStorage.getItem(url)) {
return Observable.of(JSON.parse(cached));
} else {
return this.http.get(url)
.map(resp => {
sessionStorage.setItem(url, resp.text());
return resp.json();
});
}
}
}
FYI, sessionStorage limit is 5M(or 4.75M). So, it should not be used like this for large set of data.
------ edit -------------
If you want to have refreshed data with F5, which usesmemory data instead of sessionStorage;
@Injectable()
export class HttpCache {
cached: any = {}; // this will store data
constructor(private http: Http) {}
get(url: string): Observable<any> {
if (this.cached[url]) {
return Observable.of(this.cached[url]));
} else {
return this.http.get(url)
.map(resp => {
this.cached[url] = resp.text();
return resp.json();
});
}
}
}
If you will store in session Storage then How will you make sure that Session storage is destroyed when you leave the app ?
– Gags
Jul 30 '17 at 13:04
@Gags sessionStorage.clear();
– Cassiano Montanari
Mar 23 '18 at 12:24
but this introduces unexpected behavior for the user. When the user hits F5 or refresh button of the browser, then he expects fresh data from server. But actually he is getting outdated data from localStorage. Bug reports, support tickets, etc. incoming... As the namesessionStorage
says, I would use it only for data that is expected to be consistent for the whole session.
– MA-Maddin
Mar 31 '18 at 22:28
@MA-Maddin as I stated "I used it to avoid usage limit". If you want want data to be refreshed with F5, you need to use memory instead of sessionStorage. The answer has been edited with this approach.
– allenhwkim
Mar 31 '18 at 23:53
yep, that might be a use case. I just got triggered since everyone is talking about Cache and OP hasgetCustomer
in his example. ;) So just wanted to warn some ppl that might do not see the risks :)
– MA-Maddin
Mar 31 '18 at 23:57
add a comment |
Cacheable HTTP Response Data using Rxjs Observer/Observable + Caching + Subscription
See Code Below
*disclaimer: I am new to rxjs, so bear in mind that I may be misusing the observable/observer approach. My solution is purely a conglomeration of other solutions I found, and is the consequence of having failed to find a simple well-documented solution. Thus I am providing my complete code solution (as I would liked to have found) in hopes that it helps others.
*note, this approach is loosely based on GoogleFirebaseObservables. Unfortunately I lack the proper experience/time to replicate what they did under the hood. But the following is a simplistic way of providing asynchronous access to some cache-able data.
Situation: A 'product-list' component is tasked with displaying a list of products. The site is a single-page web app with some menu buttons that will 'filter' the products displayed on the page.
Solution: The component "subscribes" to a service method. The service method returns an array of product objects, which the component accesses through the subscription callback. The service method wraps its activity in a newly created Observer and returns the observer. Inside this observer, it searches for cached data and passes it back to the subscriber (the component) and returns. Otherwise it issues an http call to retrieve the data, subscribes to the response, where you can process that data (e.g. map the data to your own model) and then pass the data back to the subscriber.
The Code
product-list.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { ProductService } from '../../../services/product.service';
import { Product, ProductResponse } from '../../../models/Product';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
products: Product;
constructor(
private productService: ProductService
) { }
ngOnInit() {
console.log('product-list init...');
this.productService.getProducts().subscribe(products => {
console.log('product-list received updated products');
this.products = products;
});
}
}
product.service.ts
import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { Observable, Observer } from 'rxjs';
import 'rxjs/add/operator/map';
import { Product, ProductResponse } from '../models/Product';
@Injectable()
export class ProductService {
products: Product;
constructor(
private http:Http
) {
console.log('product service init. calling http to get products...');
}
getProducts():Observable<Product>{
//wrap getProducts around an Observable to make it async.
let productsObservable$ = Observable.create((observer: Observer<Product>) => {
//return products if it was previously fetched
if(this.products){
console.log('## returning existing products');
observer.next(this.products);
return observer.complete();
}
//Fetch products from REST API
console.log('** products do not yet exist; fetching from rest api...');
let headers = new Headers();
this.http.get('http://localhost:3000/products/', {headers: headers})
.map(res => res.json()).subscribe((response:ProductResponse) => {
console.log('productResponse: ', response);
let productlist = Product.fromJsonList(response.products); //convert service observable to product
this.products = productlist;
observer.next(productlist);
});
});
return productsObservable$;
}
}
product.ts (the model)
export interface ProductResponse {
success: boolean;
msg: string;
products: Product;
}
export class Product {
product_id: number;
sku: string;
product_title: string;
..etc...
constructor(product_id: number,
sku: string,
product_title: string,
...etc...
){
//typescript will not autoassign the formal parameters to related properties for exported classes.
this.product_id = product_id;
this.sku = sku;
this.product_title = product_title;
...etc...
}
//Class method to convert products within http response to pure array of Product objects.
//Caller: product.service:getProducts()
static fromJsonList(products:any): Product {
let mappedArray = products.map(Product.fromJson);
return mappedArray;
}
//add more parameters depending on your database entries and constructor
static fromJson({
product_id,
sku,
product_title,
...etc...
}): Product {
return new Product(
product_id,
sku,
product_title,
...etc...
);
}
}
Here is a sample of the output I see when I load the page in Chrome. Note that on the initial load, the products are fetched from http (call to my node rest service, which is running locally on port 3000). When I then click to navigate to a 'filtered' view of the products, the products are found in cache.
My Chrome Log (console):
core.es5.js:2925 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
app.component.ts:19 app.component url: /products
product.service.ts:15 product service init. calling http to get products...
product-list.component.ts:18 product-list init...
product.service.ts:29 ** products do not yet exist; fetching from rest api...
product.service.ts:33 productResponse: {success: true, msg: "Products found", products: Array(23)}
product-list.component.ts:20 product-list received updated products
...[clicked a menu button to filter the products]...
app.component.ts:19 app.component url: /products/chocolatechip
product-list.component.ts:18 product-list init...
product.service.ts:24 ## returning existing products
product-list.component.ts:20 product-list received updated products
Conclusion: This is the simplest way I've found (so far) to implement cacheable http response data. In my angular app, each time I navigate to a different view of the products, the product-list component reloads. ProductService seems to be a shared instance, so the local cache of 'products: Product' in the ProductService is retained during navigation, and subsequent calls to "GetProducts()" returns the cached value. One final note, I've read comments about how observables/subscriptions need to be closed when you're finished to prevent 'memory leaks'. I've not included this here, but it's something to keep in mind.
1
Note - I've since found a more powerful solution, involving RxJS BehaviorSubjects, which simplifies the code and dramatically cuts down on 'overhead'. In products.service.ts, 1. import { BehaviorSubject } from 'rxjs'; 2. change 'products:Product' into 'product$: BehaviorSubject<Product> = new BehaviorSubject<Product>();' 3. Now you can simply call the http without returning anything. http_getProducts(){this.http.get(...).map(res => res.json()).subscribe(products => this.product$.next(products))};
– ObjectiveTC
Oct 2 '17 at 6:41
1
The local variable 'product$' is a behaviorSubject, which will both EMIT and STORE the latest products (from the product$.next(..) call in part 3). Now in your components, inject the service as normal. You get the most recently assigned value of product$ using productService.product$.value. Or subscribe to product$ if you want to perform an action whenever product$ receives a new value (i.e., the product$.next(...) function is called in part 3).
– ObjectiveTC
Oct 2 '17 at 6:41
1
Eg, in products.component.ts... this.productService.product$ .takeUntil(this.ngUnsubscribe) .subscribe((products) => {this.category); let filteredProducts = this.productService.getProductsByCategory(this.category); this.products = filteredProducts; });
– ObjectiveTC
Oct 2 '17 at 6:41
1
An important note about unsubscribing from observables: ".takeUntil(this.ngUnsubscribe)". See this stack overflow question/answer, which appears to show the 'de-facto' recommended way to unsubscribe from events: stackoverflow.com/questions/38008334/…
– ObjectiveTC
Oct 2 '17 at 6:47
1
The alternative is to the .first() or .take(1) if the observable is only meant to receive data once. All other 'infinite streams' of observables should be unsubscribed in 'ngOnDestroy()', and if you don't then you may end up with duplicate 'observable' callbacks. stackoverflow.com/questions/28007777/…
– ObjectiveTC
Oct 2 '17 at 6:47
add a comment |
I assume that @ngx-cache/core could be useful to maintain caching features for the http calls, especially if the HTTP call is made both on browser and server platforms.
Let's say we have the following method:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
You can use the Cached
decorator of @ngx-cache/core to store the returned value from the method making the HTTP call at the cache storage
(the storage
can be configurable, please check the implementation at ng-seed/universal) - right on the first execution. The next times the method is invoked (no matter on browser or server platform), the value is retrieved from the cache storage
.
import { Cached } from '@ngx-cache/core';
...
@Cached('get-customer') // the cache key/identifier
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
There's also the possibility to use caching methods (has
, get
, set
) using the caching API.
anyclass.ts
...
import { CacheService } from '@ngx-cache/core';
@Injectable()
export class AnyClass {
constructor(private readonly cache: CacheService) {
// note that CacheService is injected into a private property of AnyClass
}
// will retrieve 'some string value'
getSomeStringValue(): string {
if (this.cache.has('some-string'))
return this.cache.get('some-string');
this.cache.set('some-string', 'some string value');
return 'some string value';
}
}
Here are the list of packages, both for client-side and server-side caching:
@ngx-cache/core: cache utility
@ngx-cache/platform-browser: SPA/Browser platform implementation
@ngx-cache/platform-server: server platform implementation
@ngx-cache/fs-storage: storage utility (required for server platform)
add a comment |
rxjs 5.3.0
I haven't been happy with .map(myFunction).publishReplay(1).refCount()
With multiple subscribers, .map()
executes myFunction
twice in some cases (I expect it to only execute once). One fix seems to be publishReplay(1).refCount().take(1)
Another thing you can do, is just not use refCount()
and make the Observable hot right away:
let obs = this.http.get('my/data.json').publishReplay(1);
obs.connect();
return obs;
This will start the HTTP request regardless of subscribers. I'm not sure if unsubscribing before the HTTP GET finishes will cancel it or not.
add a comment |
What we want to do, is ensure that this does not cause multiple network requests.
My personal favourite is to make use of async
methods for calls that make network requests. The methods themselves don't return a value, instead they update a BehaviorSubject
within the same service, which components will subscribe to.
Now Why use a BehaviorSubject
instead of an Observable
? Because,
- Upon subscription BehaviorSubject returns the last value whereas A regular observable only triggers when it receives an
onnext
. - If you want to retrieve the last value of the BehaviorSubject in a non-observable code (without a subscription), you can use the
getValue()
method.
Example:
customer.service.ts
public customers$: BehaviorSubject<Customer> = new BehaviorSubject();
public async getCustomers(): Promise<void> {
let customers = await this.httpClient.post<LogEntry>(this.endPoint, criteria).toPromise();
if (customers)
this.customers$.next(customers);
}
Then, wherever required, we can just subscribe to customers$
.
public ngOnInit(): void {
this.customerService.customers$
.subscribe((customers: Customer) => this.customerList = customers);
}
Or maybe you want to use it directly in a template
<li *ngFor="let customer of customerService.customers$ | async"> ... </li>
So now, until you make another call to getCustomers
, the data is retained in the customers$
BehaviorSubject.
So what if you want to refresh this data? just make a call to getCustomers()
public async refresh(): Promise<void> {
try {
await this.customerService.getCustomers();
}
catch (e) {
// request failed, handle exception
console.error(e);
}
}
Using this method, we don't have to explicitly retain the data between subsequent network calls as it's handled by the BehaviorSubject
.
PS: Usually when a component gets destroyed it's a good practice to get rid of the subscriptions, for that you can use the method suggested in this answer.
add a comment |
Just call share() after map and before any subscribe.
In my case, I have a generic service (RestClientService.ts) who is making the rest call, extracting data, check for errors and returning observable to a concrete implementation service (f.ex.: ContractClientService.ts), finally this concrete implementation returns observable to de ContractComponent.ts, and this one subscribe to update the view.
RestClientService.ts:
export abstract class RestClientService<T extends BaseModel> {
public GetAll = (path: string, property: string): Observable<T> => {
let fullPath = this.actionUrl + path;
let observable = this._http.get(fullPath).map(res => this.extractData(res, property));
observable = observable.share(); //allows multiple subscribers without making again the http request
observable.subscribe(
(res) => {},
error => this.handleError2(error, "GetAll", fullPath),
() => {}
);
return observable;
}
private extractData(res: Response, property: string) {
...
}
private handleError2(error: any, method: string, path: string) {
...
}
}
ContractService.ts:
export class ContractService extends RestClientService<Contract> {
private GET_ALL_ITEMS_REST_URI_PATH = "search";
private GET_ALL_ITEMS_PROPERTY_PATH = "contract";
public getAllItems(): Observable<Contract> {
return this.GetAll(this.GET_ALL_ITEMS_REST_URI_PATH, this.GET_ALL_ITEMS_PROPERTY_PATH);
}
}
ContractComponent.ts:
export class ContractComponent implements OnInit {
getAllItems() {
this.rcService.getAllItems().subscribe((data) => {
this.items = data;
});
}
}
add a comment |
I wrote a cache class,
/**
* Caches results returned from given fetcher callback for given key,
* up to maxItems results, deletes the oldest results when full (FIFO).
*/
export class StaticCache
{
static cachedData: Map<string, any> = new Map<string, any>();
static maxItems: number = 400;
static get(key: string){
return this.cachedData.get(key);
}
static getOrFetch(key: string, fetcher: (string) => any): any {
let value = this.cachedData.get(key);
if (value != null){
console.log("Cache HIT! (fetcher)");
return value;
}
console.log("Cache MISS... (fetcher)");
value = fetcher(key);
this.add(key, value);
return value;
}
static add(key, value){
this.cachedData.set(key, value);
this.deleteOverflowing();
}
static deleteOverflowing(): void {
if (this.cachedData.size > this.maxItems) {
this.deleteOldest(this.cachedData.size - this.maxItems);
}
}
/// A Map object iterates its elements in insertion order — a for...of loop returns an array of [key, value] for each iteration.
/// However that seems not to work. Trying with forEach.
static deleteOldest(howMany: number): void {
//console.debug("Deleting oldest " + howMany + " of " + this.cachedData.size);
let iterKeys = this.cachedData.keys();
let item: IteratorResult<string>;
while (howMany-- > 0 && (item = iterKeys.next(), !item.done)){
//console.debug(" Deleting: " + item.value);
this.cachedData.delete(item.value); // Deleting while iterating should be ok in JS.
}
}
static clear(): void {
this.cachedData = new Map<string, any>();
}
}
It's all static because of how we use it, but feel free to make it a normal class and a service. I'm not sure if angular keeps a single instance for the whole time though (new to Angular2).
And this is how I use it:
let httpService: Http = this.http;
function fetcher(url: string): Observable<any> {
console.log(" Fetching URL: " + url);
return httpService.get(url).map((response: Response) => {
if (!response) return null;
if (typeof response.json() !== "array")
throw new Error("Graph REST should return an array of vertices.");
let items: any = graphService.fromJSONarray(response.json(), httpService);
return array ? items : items[0];
});
}
// If data is a link, return a result of a service call.
if (this.data[verticesLabel][name]["link"] || this.data[verticesLabel][name]["_type"] == "link")
{
// Make an HTTP call.
let url = this.data[verticesLabel][name]["link"];
let cachedObservable: Observable<any> = StaticCache.getOrFetch(url, fetcher);
if (!cachedObservable)
throw new Error("Failed loading link: " + url);
return cachedObservable;
}
I assume there could be a more clever way, which would use some Observable
tricks but this was just fine for my purposes.
add a comment |
Just use this cache layer, it does everything you requires, and even manage cache for ajax requests.
http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html
It's this much easy to use
@Component({
selector: 'home',
templateUrl: './html/home.component.html',
styleUrls: ['./css/home.component.css'],
})
export class HomeComponent {
constructor(AjaxService:AjaxService){
AjaxService.postCache("/api/home/articles").subscribe(values=>{console.log(values);this.articles=values;});
}
articles={1:[{data:[{title:"first",sort_text:"description"},{title:"second",sort_text:"description"}],type:"Open Source Works"}]};
}
The layer(as an inject-able angular service) is
import { Injectable } from '@angular/core';
import { Http, Response} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import './../rxjs/operator'
@Injectable()
export class AjaxService {
public data:Object={};
/*
private dataObservable:Observable<boolean>;
*/
private dataObserver:Array<any>=;
private loading:Object={};
private links:Object={};
counter:number=-1;
constructor (private http: Http) {
}
private loadPostCache(link:string){
if(!this.loading[link]){
this.loading[link]=true;
this.links[link].forEach(a=>this.dataObserver[a].next(false));
this.http.get(link)
.map(this.setValue)
.catch(this.handleError).subscribe(
values => {
this.data[link] = values;
delete this.loading[link];
this.links[link].forEach(a=>this.dataObserver[a].next(false));
},
error => {
delete this.loading[link];
}
);
}
}
private setValue(res: Response) {
return res.json() || { };
}
private handleError (error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
postCache(link:string): Observable<Object>{
return Observable.create(observer=> {
if(this.data.hasOwnProperty(link)){
observer.next(this.data[link]);
}
else{
let _observable=Observable.create(_observer=>{
this.counter=this.counter+1;
this.dataObserver[this.counter]=_observer;
this.links.hasOwnProperty(link)?this.links[link].push(this.counter):(this.links[link]=[this.counter]);
_observer.next(false);
});
this.loadPostCache(link);
_observable.subscribe(status=>{
if(status){
observer.next(this.data[link]);
}
}
);
}
});
}
}
add a comment |
It's .publishReplay(1).refCount();
or .publishLast().refCount();
since Angular Http observables complete after request.
This simple class caches the result so you can subscribe to .value many times and makes only 1 request. You can also use .reload() to make new request and publish data.
You can use it like:
let res = new RestResource(() => this.http.get('inline.bundleo.js'));
res.status.subscribe((loading)=>{
console.log('STATUS=',loading);
});
res.value.subscribe((value) => {
console.log('VALUE=', value);
});
and the source:
export class RestResource {
static readonly LOADING: string = 'RestResource_Loading';
static readonly ERROR: string = 'RestResource_Error';
static readonly IDLE: string = 'RestResource_Idle';
public value: Observable<any>;
public status: Observable<string>;
private loadStatus: Observer<any>;
private reloader: Observable<any>;
private reloadTrigger: Observer<any>;
constructor(requestObservableFn: () => Observable<any>) {
this.status = Observable.create((o) => {
this.loadStatus = o;
});
this.reloader = Observable.create((o: Observer<any>) => {
this.reloadTrigger = o;
});
this.value = this.reloader.startWith(null).switchMap(() => {
if (this.loadStatus) {
this.loadStatus.next(RestResource.LOADING);
}
return requestObservableFn()
.map((res) => {
if (this.loadStatus) {
this.loadStatus.next(RestResource.IDLE);
}
return res;
}).catch((err)=>{
if (this.loadStatus) {
this.loadStatus.next(RestResource.ERROR);
}
return Observable.of(null);
});
}).publishReplay(1).refCount();
}
reload() {
this.reloadTrigger.next(null);
}
}
add a comment |
You can build simple class Cacheable<> that helps managing data retrieved from http server with multiple subscribers:
declare type GetDataHandler<T> = () => Observable<T>;
export class Cacheable<T> {
protected data: T;
protected subjectData: Subject<T>;
protected observableData: Observable<T>;
public getHandler: GetDataHandler<T>;
constructor() {
this.subjectData = new ReplaySubject(1);
this.observableData = this.subjectData.asObservable();
}
public getData(): Observable<T> {
if (!this.getHandler) {
throw new Error("getHandler is not defined");
}
if (!this.data) {
this.getHandler().map((r: T) => {
this.data = r;
return r;
}).subscribe(
result => this.subjectData.next(result),
err => this.subjectData.error(err)
);
}
return this.observableData;
}
public resetCache(): void {
this.data = null;
}
public refresh(): void {
this.resetCache();
this.getData();
}
}
Usage
Declare Cacheable<> object (presumably as part of the service):
list: Cacheable<string> = new Cacheable<string>();
and handler:
this.list.getHandler = () => {
// get data from server
return this.http.get(url)
.map((r: Response) => r.json() as string);
}
Call from a component:
//gets data from server
List.getData().subscribe(…)
You can have several components subscribed to it.
More details and code example are here: http://devinstance.net/articles/20171021/rxjs-cacheable
add a comment |
Great answers.
Or you could do this:
This is from latest version of rxjs. I am using 5.5.7 version of RxJS
import {share} from "rxjs/operators";
this.http.get('/someUrl').pipe(share());
add a comment |
You could simply use ngx-cacheable! It better suits your scenario.
The benefit of using this
- It calls rest API only once, caches the response & returns the same for following requests.
- Can call API as required after create/ update/ delete operation.
So, Your service class would be something like this -
import { Injectable } from '@angular/core';
import { Cacheable, CacheBuster } from 'ngx-cacheable';
const customerNotifier = new Subject();
@Injectable()
export class customersService {
// relieves all its caches when any new value is emitted in the stream using notifier
@Cacheable({
cacheBusterObserver: customerNotifier,
async: true
})
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
// notifies the observer to refresh the data
@CacheBuster({
cacheBusterNotifier: customerNotifier
})
addCustomer() {
// some code
}
// notifies the observer to refresh the data
@CacheBuster({
cacheBusterNotifier: customerNotifier
})
updateCustomer() {
// some code
}
}
Here's the link for more reference.
add a comment |
This is exactly what I have created the library ngx-rxcache for.
Take a look at it at https://github.com/adriandavidbrand/ngx-rxcache and see a working example at https://stackblitz.com/edit/angular-jxqaiv
add a comment |
Have you tried running the code you already have?
Because you are constructing the Observable from the promise resulting from getJSON()
, the network request is made before anyone subscribes. And the resulting promise is shared by all subscribers.
var promise = jQuery.getJSON(requestUrl); // network call is executed now
var o = Rx.Observable.fromPromise(promise); // just wraps it in an observable
o.subscribe(...); // does not trigger network call
o.subscribe(...); // does not trigger network call
// ...
i've edit the question to make it Angular 2 specific
– Angular University
Mar 29 '16 at 14:00
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%2f36271899%2fwhat-is-the-correct-way-to-share-the-result-of-an-angular-http-network-call-in-r%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
21 Answers
21
active
oldest
votes
21 Answers
21
active
oldest
votes
active
oldest
votes
active
oldest
votes
Cache the data and if available cached, return this otherwise make the HTTP request.
import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of'; //proper way to import the 'of' operator
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/map';
import {Data} from './data';
@Injectable()
export class DataService {
private url:string = 'https://cors-test.appspot.com/test';
private data: Data;
private observable: Observable<any>;
constructor(private http:Http) {}
getData() {
if(this.data) {
// if `data` is available just return it as `Observable`
return Observable.of(this.data);
} else if(this.observable) {
// if `this.observable` is set then the request is in progress
// return the `Observable` for the ongoing request
return this.observable;
} else {
// example header (not necessary)
let headers = new Headers();
headers.append('Content-Type', 'application/json');
// create the request, store the `Observable` for subsequent subscribers
this.observable = this.http.get(this.url, {
headers: headers
})
.map(response => {
// when the cached data is available we don't need the `Observable` reference anymore
this.observable = null;
if(response.status == 400) {
return "FAILURE";
} else if(response.status == 200) {
this.data = new Data(response.json());
return this.data;
}
// make it shared so more than one subscriber can get the result
})
.share();
return this.observable;
}
}
}
Plunker example
This artile https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html is a great explanation how to cache with shareReplay
.
3
do()
in contrary tomap()
doesn't modify the event. You could usemap()
as well but then you had to ensure the correct value is returned at the end of the callback.
– Günter Zöchbauer
Apr 15 '16 at 16:00
3
If the call-site that does the.subscribe()
doesn't need the value you can do that because it might get justnull
(depending on whatthis.extractData
returns), but IMHO this doesn't express the intent of the code well.
– Günter Zöchbauer
Apr 15 '16 at 16:08
2
Whenthis.extraData
ends likeextraData() { if(foo) { doSomething();}}
otherwise the result of the last expression is returned which might not be what you want.
– Günter Zöchbauer
Apr 15 '16 at 19:15
7
@Günter, thank you for the code, it works. However, I am trying to understand why you are keeping track of Data and Observable separately. Wouldn't you effectively achieve the same effect by caching just Observable<Data> like this?if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
– July.Tech
Dec 15 '16 at 17:12
3
@HarleenKaur It's a class the received JSON is deserialized to, to get strong type checking and autocompletion. There is no need to use it, but it's common.
– Günter Zöchbauer
May 31 '17 at 17:22
|
show 15 more comments
Cache the data and if available cached, return this otherwise make the HTTP request.
import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of'; //proper way to import the 'of' operator
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/map';
import {Data} from './data';
@Injectable()
export class DataService {
private url:string = 'https://cors-test.appspot.com/test';
private data: Data;
private observable: Observable<any>;
constructor(private http:Http) {}
getData() {
if(this.data) {
// if `data` is available just return it as `Observable`
return Observable.of(this.data);
} else if(this.observable) {
// if `this.observable` is set then the request is in progress
// return the `Observable` for the ongoing request
return this.observable;
} else {
// example header (not necessary)
let headers = new Headers();
headers.append('Content-Type', 'application/json');
// create the request, store the `Observable` for subsequent subscribers
this.observable = this.http.get(this.url, {
headers: headers
})
.map(response => {
// when the cached data is available we don't need the `Observable` reference anymore
this.observable = null;
if(response.status == 400) {
return "FAILURE";
} else if(response.status == 200) {
this.data = new Data(response.json());
return this.data;
}
// make it shared so more than one subscriber can get the result
})
.share();
return this.observable;
}
}
}
Plunker example
This artile https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html is a great explanation how to cache with shareReplay
.
3
do()
in contrary tomap()
doesn't modify the event. You could usemap()
as well but then you had to ensure the correct value is returned at the end of the callback.
– Günter Zöchbauer
Apr 15 '16 at 16:00
3
If the call-site that does the.subscribe()
doesn't need the value you can do that because it might get justnull
(depending on whatthis.extractData
returns), but IMHO this doesn't express the intent of the code well.
– Günter Zöchbauer
Apr 15 '16 at 16:08
2
Whenthis.extraData
ends likeextraData() { if(foo) { doSomething();}}
otherwise the result of the last expression is returned which might not be what you want.
– Günter Zöchbauer
Apr 15 '16 at 19:15
7
@Günter, thank you for the code, it works. However, I am trying to understand why you are keeping track of Data and Observable separately. Wouldn't you effectively achieve the same effect by caching just Observable<Data> like this?if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
– July.Tech
Dec 15 '16 at 17:12
3
@HarleenKaur It's a class the received JSON is deserialized to, to get strong type checking and autocompletion. There is no need to use it, but it's common.
– Günter Zöchbauer
May 31 '17 at 17:22
|
show 15 more comments
Cache the data and if available cached, return this otherwise make the HTTP request.
import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of'; //proper way to import the 'of' operator
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/map';
import {Data} from './data';
@Injectable()
export class DataService {
private url:string = 'https://cors-test.appspot.com/test';
private data: Data;
private observable: Observable<any>;
constructor(private http:Http) {}
getData() {
if(this.data) {
// if `data` is available just return it as `Observable`
return Observable.of(this.data);
} else if(this.observable) {
// if `this.observable` is set then the request is in progress
// return the `Observable` for the ongoing request
return this.observable;
} else {
// example header (not necessary)
let headers = new Headers();
headers.append('Content-Type', 'application/json');
// create the request, store the `Observable` for subsequent subscribers
this.observable = this.http.get(this.url, {
headers: headers
})
.map(response => {
// when the cached data is available we don't need the `Observable` reference anymore
this.observable = null;
if(response.status == 400) {
return "FAILURE";
} else if(response.status == 200) {
this.data = new Data(response.json());
return this.data;
}
// make it shared so more than one subscriber can get the result
})
.share();
return this.observable;
}
}
}
Plunker example
This artile https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html is a great explanation how to cache with shareReplay
.
Cache the data and if available cached, return this otherwise make the HTTP request.
import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of'; //proper way to import the 'of' operator
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/map';
import {Data} from './data';
@Injectable()
export class DataService {
private url:string = 'https://cors-test.appspot.com/test';
private data: Data;
private observable: Observable<any>;
constructor(private http:Http) {}
getData() {
if(this.data) {
// if `data` is available just return it as `Observable`
return Observable.of(this.data);
} else if(this.observable) {
// if `this.observable` is set then the request is in progress
// return the `Observable` for the ongoing request
return this.observable;
} else {
// example header (not necessary)
let headers = new Headers();
headers.append('Content-Type', 'application/json');
// create the request, store the `Observable` for subsequent subscribers
this.observable = this.http.get(this.url, {
headers: headers
})
.map(response => {
// when the cached data is available we don't need the `Observable` reference anymore
this.observable = null;
if(response.status == 400) {
return "FAILURE";
} else if(response.status == 200) {
this.data = new Data(response.json());
return this.data;
}
// make it shared so more than one subscriber can get the result
})
.share();
return this.observable;
}
}
}
Plunker example
This artile https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html is a great explanation how to cache with shareReplay
.
edited Aug 31 '18 at 9:01
Simon_Weaver
71k61444516
71k61444516
answered Mar 29 '16 at 17:56


Günter Zöchbauer
314k65932877
314k65932877
3
do()
in contrary tomap()
doesn't modify the event. You could usemap()
as well but then you had to ensure the correct value is returned at the end of the callback.
– Günter Zöchbauer
Apr 15 '16 at 16:00
3
If the call-site that does the.subscribe()
doesn't need the value you can do that because it might get justnull
(depending on whatthis.extractData
returns), but IMHO this doesn't express the intent of the code well.
– Günter Zöchbauer
Apr 15 '16 at 16:08
2
Whenthis.extraData
ends likeextraData() { if(foo) { doSomething();}}
otherwise the result of the last expression is returned which might not be what you want.
– Günter Zöchbauer
Apr 15 '16 at 19:15
7
@Günter, thank you for the code, it works. However, I am trying to understand why you are keeping track of Data and Observable separately. Wouldn't you effectively achieve the same effect by caching just Observable<Data> like this?if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
– July.Tech
Dec 15 '16 at 17:12
3
@HarleenKaur It's a class the received JSON is deserialized to, to get strong type checking and autocompletion. There is no need to use it, but it's common.
– Günter Zöchbauer
May 31 '17 at 17:22
|
show 15 more comments
3
do()
in contrary tomap()
doesn't modify the event. You could usemap()
as well but then you had to ensure the correct value is returned at the end of the callback.
– Günter Zöchbauer
Apr 15 '16 at 16:00
3
If the call-site that does the.subscribe()
doesn't need the value you can do that because it might get justnull
(depending on whatthis.extractData
returns), but IMHO this doesn't express the intent of the code well.
– Günter Zöchbauer
Apr 15 '16 at 16:08
2
Whenthis.extraData
ends likeextraData() { if(foo) { doSomething();}}
otherwise the result of the last expression is returned which might not be what you want.
– Günter Zöchbauer
Apr 15 '16 at 19:15
7
@Günter, thank you for the code, it works. However, I am trying to understand why you are keeping track of Data and Observable separately. Wouldn't you effectively achieve the same effect by caching just Observable<Data> like this?if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
– July.Tech
Dec 15 '16 at 17:12
3
@HarleenKaur It's a class the received JSON is deserialized to, to get strong type checking and autocompletion. There is no need to use it, but it's common.
– Günter Zöchbauer
May 31 '17 at 17:22
3
3
do()
in contrary to map()
doesn't modify the event. You could use map()
as well but then you had to ensure the correct value is returned at the end of the callback.– Günter Zöchbauer
Apr 15 '16 at 16:00
do()
in contrary to map()
doesn't modify the event. You could use map()
as well but then you had to ensure the correct value is returned at the end of the callback.– Günter Zöchbauer
Apr 15 '16 at 16:00
3
3
If the call-site that does the
.subscribe()
doesn't need the value you can do that because it might get just null
(depending on what this.extractData
returns), but IMHO this doesn't express the intent of the code well.– Günter Zöchbauer
Apr 15 '16 at 16:08
If the call-site that does the
.subscribe()
doesn't need the value you can do that because it might get just null
(depending on what this.extractData
returns), but IMHO this doesn't express the intent of the code well.– Günter Zöchbauer
Apr 15 '16 at 16:08
2
2
When
this.extraData
ends like extraData() { if(foo) { doSomething();}}
otherwise the result of the last expression is returned which might not be what you want.– Günter Zöchbauer
Apr 15 '16 at 19:15
When
this.extraData
ends like extraData() { if(foo) { doSomething();}}
otherwise the result of the last expression is returned which might not be what you want.– Günter Zöchbauer
Apr 15 '16 at 19:15
7
7
@Günter, thank you for the code, it works. However, I am trying to understand why you are keeping track of Data and Observable separately. Wouldn't you effectively achieve the same effect by caching just Observable<Data> like this?
if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
– July.Tech
Dec 15 '16 at 17:12
@Günter, thank you for the code, it works. However, I am trying to understand why you are keeping track of Data and Observable separately. Wouldn't you effectively achieve the same effect by caching just Observable<Data> like this?
if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
– July.Tech
Dec 15 '16 at 17:12
3
3
@HarleenKaur It's a class the received JSON is deserialized to, to get strong type checking and autocompletion. There is no need to use it, but it's common.
– Günter Zöchbauer
May 31 '17 at 17:22
@HarleenKaur It's a class the received JSON is deserialized to, to get strong type checking and autocompletion. There is no need to use it, but it's common.
– Günter Zöchbauer
May 31 '17 at 17:22
|
show 15 more comments
Per @Cristian suggestion, this is one way that works well for HTTP observables, that only emit once and then they complete:
getCustomer() {
return this.http.get('/someUrl')
.map(res => res.json()).publishLast().refCount();
}
There are a couple of problems with using this approach - the returned observable cannot be cancelled or retried. This might not be an issue for you, but then again it might. If this is a problem then theshare
operator might be a reasonable choice (albeit with some nasty edge cases). For a deep dive discussion on the options see comments section in this blog post: blog.jhades.org/…
– Christian
Apr 3 '16 at 8:46
1
Small clarification... Although strictly the source observable being shared bypublishLast().refCount()
cannot be cancelled, once all subscriptions to the observable returned byrefCount
have been cancelled, the net effect is the source observable will be unsubscribed, cancelling it if it where "inflight"
– Christian
Apr 4 '16 at 6:43
@Christian Hey, can you explain what you mean by saying "cannot be cancelled or retried"? Thanks.
– undefined
Jun 7 '17 at 5:19
add a comment |
Per @Cristian suggestion, this is one way that works well for HTTP observables, that only emit once and then they complete:
getCustomer() {
return this.http.get('/someUrl')
.map(res => res.json()).publishLast().refCount();
}
There are a couple of problems with using this approach - the returned observable cannot be cancelled or retried. This might not be an issue for you, but then again it might. If this is a problem then theshare
operator might be a reasonable choice (albeit with some nasty edge cases). For a deep dive discussion on the options see comments section in this blog post: blog.jhades.org/…
– Christian
Apr 3 '16 at 8:46
1
Small clarification... Although strictly the source observable being shared bypublishLast().refCount()
cannot be cancelled, once all subscriptions to the observable returned byrefCount
have been cancelled, the net effect is the source observable will be unsubscribed, cancelling it if it where "inflight"
– Christian
Apr 4 '16 at 6:43
@Christian Hey, can you explain what you mean by saying "cannot be cancelled or retried"? Thanks.
– undefined
Jun 7 '17 at 5:19
add a comment |
Per @Cristian suggestion, this is one way that works well for HTTP observables, that only emit once and then they complete:
getCustomer() {
return this.http.get('/someUrl')
.map(res => res.json()).publishLast().refCount();
}
Per @Cristian suggestion, this is one way that works well for HTTP observables, that only emit once and then they complete:
getCustomer() {
return this.http.get('/someUrl')
.map(res => res.json()).publishLast().refCount();
}
edited Mar 23 '17 at 15:50
answered Mar 29 '16 at 22:09


Angular University
29.9k125576
29.9k125576
There are a couple of problems with using this approach - the returned observable cannot be cancelled or retried. This might not be an issue for you, but then again it might. If this is a problem then theshare
operator might be a reasonable choice (albeit with some nasty edge cases). For a deep dive discussion on the options see comments section in this blog post: blog.jhades.org/…
– Christian
Apr 3 '16 at 8:46
1
Small clarification... Although strictly the source observable being shared bypublishLast().refCount()
cannot be cancelled, once all subscriptions to the observable returned byrefCount
have been cancelled, the net effect is the source observable will be unsubscribed, cancelling it if it where "inflight"
– Christian
Apr 4 '16 at 6:43
@Christian Hey, can you explain what you mean by saying "cannot be cancelled or retried"? Thanks.
– undefined
Jun 7 '17 at 5:19
add a comment |
There are a couple of problems with using this approach - the returned observable cannot be cancelled or retried. This might not be an issue for you, but then again it might. If this is a problem then theshare
operator might be a reasonable choice (albeit with some nasty edge cases). For a deep dive discussion on the options see comments section in this blog post: blog.jhades.org/…
– Christian
Apr 3 '16 at 8:46
1
Small clarification... Although strictly the source observable being shared bypublishLast().refCount()
cannot be cancelled, once all subscriptions to the observable returned byrefCount
have been cancelled, the net effect is the source observable will be unsubscribed, cancelling it if it where "inflight"
– Christian
Apr 4 '16 at 6:43
@Christian Hey, can you explain what you mean by saying "cannot be cancelled or retried"? Thanks.
– undefined
Jun 7 '17 at 5:19
There are a couple of problems with using this approach - the returned observable cannot be cancelled or retried. This might not be an issue for you, but then again it might. If this is a problem then the
share
operator might be a reasonable choice (albeit with some nasty edge cases). For a deep dive discussion on the options see comments section in this blog post: blog.jhades.org/…– Christian
Apr 3 '16 at 8:46
There are a couple of problems with using this approach - the returned observable cannot be cancelled or retried. This might not be an issue for you, but then again it might. If this is a problem then the
share
operator might be a reasonable choice (albeit with some nasty edge cases). For a deep dive discussion on the options see comments section in this blog post: blog.jhades.org/…– Christian
Apr 3 '16 at 8:46
1
1
Small clarification... Although strictly the source observable being shared by
publishLast().refCount()
cannot be cancelled, once all subscriptions to the observable returned by refCount
have been cancelled, the net effect is the source observable will be unsubscribed, cancelling it if it where "inflight"– Christian
Apr 4 '16 at 6:43
Small clarification... Although strictly the source observable being shared by
publishLast().refCount()
cannot be cancelled, once all subscriptions to the observable returned by refCount
have been cancelled, the net effect is the source observable will be unsubscribed, cancelling it if it where "inflight"– Christian
Apr 4 '16 at 6:43
@Christian Hey, can you explain what you mean by saying "cannot be cancelled or retried"? Thanks.
– undefined
Jun 7 '17 at 5:19
@Christian Hey, can you explain what you mean by saying "cannot be cancelled or retried"? Thanks.
– undefined
Jun 7 '17 at 5:19
add a comment |
UPDATE: Ben Lesh says the next minor release after 5.2.0, you'll be able to just call shareReplay() to truly cache.
PREVIOUSLY.....
Firstly, don't use share() or publishReplay(1).refCount(), they are the same and the problem with it, is that it only shares if connections are made while the observable is active, if you connect after it completes, it creates a new observable again, translation, not really caching.
Birowski gave the right solution above, which is to use ReplaySubject. ReplaySubject will caches the values you give it (bufferSize) in our case 1. It will not create a new observable like share() once refCount reaches zero and you make a new connection, which is the right behavior for caching.
Here's a reusable function
export function cacheable<T>(o: Observable<T>): Observable<T> {
let replay = new ReplaySubject<T>(1);
o.subscribe(
x => replay.next(x),
x => replay.error(x),
() => replay.complete()
);
return replay.asObservable();
}
Here's how to use it
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { cacheable } from '../utils/rxjs-functions';
@Injectable()
export class SettingsService {
_cache: Observable<any>;
constructor(private _http: Http, ) { }
refresh = () => {
if (this._cache) {
return this._cache;
}
return this._cache = cacheable<any>(this._http.get('YOUR URL'));
}
}
Below is a more advance version of the cacheable function This one allows has its own lookup table + the ability to provide a custom lookup table. This way, you don't have to check this._cache like in the above example. Also notice that instead of passing the observable as the first argument, you pass a function which returns the observables, this is because Angular's Http executes right away, so by returning a lazy executed function, we can decide not to call it if it's already in our cache.
let cacheableCache: { [key: string]: Observable<any> } = {};
export function cacheable<T>(returnObservable: () => Observable<T>, key?: string, customCache?: { [key: string]: Observable<T> }): Observable<T> {
if (!!key && (customCache || cacheableCache)[key]) {
return (customCache || cacheableCache)[key] as Observable<T>;
}
let replay = new ReplaySubject<T>(1);
returnObservable().subscribe(
x => replay.next(x),
x => replay.error(x),
() => replay.complete()
);
let observable = replay.asObservable();
if (!!key) {
if (!!customCache) {
customCache[key] = observable;
} else {
cacheableCache[key] = observable;
}
}
return observable;
}
Usage:
getData() => cacheable(this._http.get("YOUR URL"), "this is key for my cache")
add a comment |
UPDATE: Ben Lesh says the next minor release after 5.2.0, you'll be able to just call shareReplay() to truly cache.
PREVIOUSLY.....
Firstly, don't use share() or publishReplay(1).refCount(), they are the same and the problem with it, is that it only shares if connections are made while the observable is active, if you connect after it completes, it creates a new observable again, translation, not really caching.
Birowski gave the right solution above, which is to use ReplaySubject. ReplaySubject will caches the values you give it (bufferSize) in our case 1. It will not create a new observable like share() once refCount reaches zero and you make a new connection, which is the right behavior for caching.
Here's a reusable function
export function cacheable<T>(o: Observable<T>): Observable<T> {
let replay = new ReplaySubject<T>(1);
o.subscribe(
x => replay.next(x),
x => replay.error(x),
() => replay.complete()
);
return replay.asObservable();
}
Here's how to use it
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { cacheable } from '../utils/rxjs-functions';
@Injectable()
export class SettingsService {
_cache: Observable<any>;
constructor(private _http: Http, ) { }
refresh = () => {
if (this._cache) {
return this._cache;
}
return this._cache = cacheable<any>(this._http.get('YOUR URL'));
}
}
Below is a more advance version of the cacheable function This one allows has its own lookup table + the ability to provide a custom lookup table. This way, you don't have to check this._cache like in the above example. Also notice that instead of passing the observable as the first argument, you pass a function which returns the observables, this is because Angular's Http executes right away, so by returning a lazy executed function, we can decide not to call it if it's already in our cache.
let cacheableCache: { [key: string]: Observable<any> } = {};
export function cacheable<T>(returnObservable: () => Observable<T>, key?: string, customCache?: { [key: string]: Observable<T> }): Observable<T> {
if (!!key && (customCache || cacheableCache)[key]) {
return (customCache || cacheableCache)[key] as Observable<T>;
}
let replay = new ReplaySubject<T>(1);
returnObservable().subscribe(
x => replay.next(x),
x => replay.error(x),
() => replay.complete()
);
let observable = replay.asObservable();
if (!!key) {
if (!!customCache) {
customCache[key] = observable;
} else {
cacheableCache[key] = observable;
}
}
return observable;
}
Usage:
getData() => cacheable(this._http.get("YOUR URL"), "this is key for my cache")
add a comment |
UPDATE: Ben Lesh says the next minor release after 5.2.0, you'll be able to just call shareReplay() to truly cache.
PREVIOUSLY.....
Firstly, don't use share() or publishReplay(1).refCount(), they are the same and the problem with it, is that it only shares if connections are made while the observable is active, if you connect after it completes, it creates a new observable again, translation, not really caching.
Birowski gave the right solution above, which is to use ReplaySubject. ReplaySubject will caches the values you give it (bufferSize) in our case 1. It will not create a new observable like share() once refCount reaches zero and you make a new connection, which is the right behavior for caching.
Here's a reusable function
export function cacheable<T>(o: Observable<T>): Observable<T> {
let replay = new ReplaySubject<T>(1);
o.subscribe(
x => replay.next(x),
x => replay.error(x),
() => replay.complete()
);
return replay.asObservable();
}
Here's how to use it
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { cacheable } from '../utils/rxjs-functions';
@Injectable()
export class SettingsService {
_cache: Observable<any>;
constructor(private _http: Http, ) { }
refresh = () => {
if (this._cache) {
return this._cache;
}
return this._cache = cacheable<any>(this._http.get('YOUR URL'));
}
}
Below is a more advance version of the cacheable function This one allows has its own lookup table + the ability to provide a custom lookup table. This way, you don't have to check this._cache like in the above example. Also notice that instead of passing the observable as the first argument, you pass a function which returns the observables, this is because Angular's Http executes right away, so by returning a lazy executed function, we can decide not to call it if it's already in our cache.
let cacheableCache: { [key: string]: Observable<any> } = {};
export function cacheable<T>(returnObservable: () => Observable<T>, key?: string, customCache?: { [key: string]: Observable<T> }): Observable<T> {
if (!!key && (customCache || cacheableCache)[key]) {
return (customCache || cacheableCache)[key] as Observable<T>;
}
let replay = new ReplaySubject<T>(1);
returnObservable().subscribe(
x => replay.next(x),
x => replay.error(x),
() => replay.complete()
);
let observable = replay.asObservable();
if (!!key) {
if (!!customCache) {
customCache[key] = observable;
} else {
cacheableCache[key] = observable;
}
}
return observable;
}
Usage:
getData() => cacheable(this._http.get("YOUR URL"), "this is key for my cache")
UPDATE: Ben Lesh says the next minor release after 5.2.0, you'll be able to just call shareReplay() to truly cache.
PREVIOUSLY.....
Firstly, don't use share() or publishReplay(1).refCount(), they are the same and the problem with it, is that it only shares if connections are made while the observable is active, if you connect after it completes, it creates a new observable again, translation, not really caching.
Birowski gave the right solution above, which is to use ReplaySubject. ReplaySubject will caches the values you give it (bufferSize) in our case 1. It will not create a new observable like share() once refCount reaches zero and you make a new connection, which is the right behavior for caching.
Here's a reusable function
export function cacheable<T>(o: Observable<T>): Observable<T> {
let replay = new ReplaySubject<T>(1);
o.subscribe(
x => replay.next(x),
x => replay.error(x),
() => replay.complete()
);
return replay.asObservable();
}
Here's how to use it
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { cacheable } from '../utils/rxjs-functions';
@Injectable()
export class SettingsService {
_cache: Observable<any>;
constructor(private _http: Http, ) { }
refresh = () => {
if (this._cache) {
return this._cache;
}
return this._cache = cacheable<any>(this._http.get('YOUR URL'));
}
}
Below is a more advance version of the cacheable function This one allows has its own lookup table + the ability to provide a custom lookup table. This way, you don't have to check this._cache like in the above example. Also notice that instead of passing the observable as the first argument, you pass a function which returns the observables, this is because Angular's Http executes right away, so by returning a lazy executed function, we can decide not to call it if it's already in our cache.
let cacheableCache: { [key: string]: Observable<any> } = {};
export function cacheable<T>(returnObservable: () => Observable<T>, key?: string, customCache?: { [key: string]: Observable<T> }): Observable<T> {
if (!!key && (customCache || cacheableCache)[key]) {
return (customCache || cacheableCache)[key] as Observable<T>;
}
let replay = new ReplaySubject<T>(1);
returnObservable().subscribe(
x => replay.next(x),
x => replay.error(x),
() => replay.complete()
);
let observable = replay.asObservable();
if (!!key) {
if (!!customCache) {
customCache[key] = observable;
} else {
cacheableCache[key] = observable;
}
}
return observable;
}
Usage:
getData() => cacheable(this._http.get("YOUR URL"), "this is key for my cache")
edited Aug 17 '17 at 7:03
taras-d
902216
902216
answered Mar 23 '17 at 1:28


Guojian Miguel Wu
29134
29134
add a comment |
add a comment |
according to this article
It turns out we can easily add caching to the observable by adding publishReplay(1) and refCount.
so inside if statements just append
.publishReplay(1)
.refCount();
to .map(...)
add a comment |
according to this article
It turns out we can easily add caching to the observable by adding publishReplay(1) and refCount.
so inside if statements just append
.publishReplay(1)
.refCount();
to .map(...)
add a comment |
according to this article
It turns out we can easily add caching to the observable by adding publishReplay(1) and refCount.
so inside if statements just append
.publishReplay(1)
.refCount();
to .map(...)
according to this article
It turns out we can easily add caching to the observable by adding publishReplay(1) and refCount.
so inside if statements just append
.publishReplay(1)
.refCount();
to .map(...)
edited Jul 7 '16 at 15:50


Günter Zöchbauer
314k65932877
314k65932877
answered Jun 10 '16 at 20:17


Ivanesses
23728
23728
add a comment |
add a comment |
rxjs 5.4.0 has a new shareReplay method.
- rx-book shareReplay()
- No docs at reactivex.io/rxjs
The author explicitly says "ideal for handling things like caching AJAX results"
rxjs PR #2443 feat(shareReplay): adds shareReplay
variant of publishReplay
shareReplay returns an observable that is the source multicasted over
a ReplaySubject. That replay subject is recycled on error from the
source, but not on completion of the source. This makes shareReplay
ideal for handling things like caching AJAX results, as it's
retryable. It's repeat behavior, however, differs from share in that
it will not repeat the source observable, rather it will repeat the
source observable's values.
Is it related to this? These docs are from 2014 though. github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/…
– Aaron Hoffman
Jun 21 '17 at 14:29
4
I tried adding .shareReplay(1, 10000) to an observable but I didn't notice any caching or behavior change. Is there a working example available?
– Aydus-Matthew
Sep 13 '17 at 1:44
Looking at the changelog github.com/ReactiveX/rxjs/blob/… It appeared earlier, was removed in v5, added back in 5.4 - that rx-book link does refer to v4, but it exists in the current LTS v5.5.6 and it's in v6. I imagine the rx-book link there is out of date.
– Jason Awbrey
Feb 4 '18 at 22:54
add a comment |
rxjs 5.4.0 has a new shareReplay method.
- rx-book shareReplay()
- No docs at reactivex.io/rxjs
The author explicitly says "ideal for handling things like caching AJAX results"
rxjs PR #2443 feat(shareReplay): adds shareReplay
variant of publishReplay
shareReplay returns an observable that is the source multicasted over
a ReplaySubject. That replay subject is recycled on error from the
source, but not on completion of the source. This makes shareReplay
ideal for handling things like caching AJAX results, as it's
retryable. It's repeat behavior, however, differs from share in that
it will not repeat the source observable, rather it will repeat the
source observable's values.
Is it related to this? These docs are from 2014 though. github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/…
– Aaron Hoffman
Jun 21 '17 at 14:29
4
I tried adding .shareReplay(1, 10000) to an observable but I didn't notice any caching or behavior change. Is there a working example available?
– Aydus-Matthew
Sep 13 '17 at 1:44
Looking at the changelog github.com/ReactiveX/rxjs/blob/… It appeared earlier, was removed in v5, added back in 5.4 - that rx-book link does refer to v4, but it exists in the current LTS v5.5.6 and it's in v6. I imagine the rx-book link there is out of date.
– Jason Awbrey
Feb 4 '18 at 22:54
add a comment |
rxjs 5.4.0 has a new shareReplay method.
- rx-book shareReplay()
- No docs at reactivex.io/rxjs
The author explicitly says "ideal for handling things like caching AJAX results"
rxjs PR #2443 feat(shareReplay): adds shareReplay
variant of publishReplay
shareReplay returns an observable that is the source multicasted over
a ReplaySubject. That replay subject is recycled on error from the
source, but not on completion of the source. This makes shareReplay
ideal for handling things like caching AJAX results, as it's
retryable. It's repeat behavior, however, differs from share in that
it will not repeat the source observable, rather it will repeat the
source observable's values.
rxjs 5.4.0 has a new shareReplay method.
- rx-book shareReplay()
- No docs at reactivex.io/rxjs
The author explicitly says "ideal for handling things like caching AJAX results"
rxjs PR #2443 feat(shareReplay): adds shareReplay
variant of publishReplay
shareReplay returns an observable that is the source multicasted over
a ReplaySubject. That replay subject is recycled on error from the
source, but not on completion of the source. This makes shareReplay
ideal for handling things like caching AJAX results, as it's
retryable. It's repeat behavior, however, differs from share in that
it will not repeat the source observable, rather it will repeat the
source observable's values.
edited Sep 13 '17 at 17:24
answered May 12 '17 at 17:15


Arlo
7381818
7381818
Is it related to this? These docs are from 2014 though. github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/…
– Aaron Hoffman
Jun 21 '17 at 14:29
4
I tried adding .shareReplay(1, 10000) to an observable but I didn't notice any caching or behavior change. Is there a working example available?
– Aydus-Matthew
Sep 13 '17 at 1:44
Looking at the changelog github.com/ReactiveX/rxjs/blob/… It appeared earlier, was removed in v5, added back in 5.4 - that rx-book link does refer to v4, but it exists in the current LTS v5.5.6 and it's in v6. I imagine the rx-book link there is out of date.
– Jason Awbrey
Feb 4 '18 at 22:54
add a comment |
Is it related to this? These docs are from 2014 though. github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/…
– Aaron Hoffman
Jun 21 '17 at 14:29
4
I tried adding .shareReplay(1, 10000) to an observable but I didn't notice any caching or behavior change. Is there a working example available?
– Aydus-Matthew
Sep 13 '17 at 1:44
Looking at the changelog github.com/ReactiveX/rxjs/blob/… It appeared earlier, was removed in v5, added back in 5.4 - that rx-book link does refer to v4, but it exists in the current LTS v5.5.6 and it's in v6. I imagine the rx-book link there is out of date.
– Jason Awbrey
Feb 4 '18 at 22:54
Is it related to this? These docs are from 2014 though. github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/…
– Aaron Hoffman
Jun 21 '17 at 14:29
Is it related to this? These docs are from 2014 though. github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/…
– Aaron Hoffman
Jun 21 '17 at 14:29
4
4
I tried adding .shareReplay(1, 10000) to an observable but I didn't notice any caching or behavior change. Is there a working example available?
– Aydus-Matthew
Sep 13 '17 at 1:44
I tried adding .shareReplay(1, 10000) to an observable but I didn't notice any caching or behavior change. Is there a working example available?
– Aydus-Matthew
Sep 13 '17 at 1:44
Looking at the changelog github.com/ReactiveX/rxjs/blob/… It appeared earlier, was removed in v5, added back in 5.4 - that rx-book link does refer to v4, but it exists in the current LTS v5.5.6 and it's in v6. I imagine the rx-book link there is out of date.
– Jason Awbrey
Feb 4 '18 at 22:54
Looking at the changelog github.com/ReactiveX/rxjs/blob/… It appeared earlier, was removed in v5, added back in 5.4 - that rx-book link does refer to v4, but it exists in the current LTS v5.5.6 and it's in v6. I imagine the rx-book link there is out of date.
– Jason Awbrey
Feb 4 '18 at 22:54
add a comment |
I starred the question, but i'll try and have a go at this.
//this will be the shared observable that
//anyone can subscribe to, get the value,
//but not cause an api request
let customer$ = new Rx.ReplaySubject(1);
getCustomer().subscribe(customer$);
//here's the first subscriber
customer$.subscribe(val => console.log('subscriber 1: ' + val));
//here's the second subscriber
setTimeout(() => {
customer$.subscribe(val => console.log('subscriber 2: ' + val));
}, 1000);
function getCustomer() {
return new Rx.Observable(observer => {
console.log('api request');
setTimeout(() => {
console.log('api response');
observer.next('customer object');
observer.complete();
}, 500);
});
}
Here's the proof :)
There is but one takeaway: getCustomer().subscribe(customer$)
We are not subscribing to the api response of getCustomer()
, we are subscribing to a ReplaySubject which is observable which is also able to subscribe to a different Observable and (and this is important) hold it's last emitted value and republish it to any of it's(ReplaySubject's) subscribers.
1
I like this approach as it makes good use of rxjs and no need to add custom logic, thank-you
– Thibs
Jan 25 '17 at 1:47
add a comment |
I starred the question, but i'll try and have a go at this.
//this will be the shared observable that
//anyone can subscribe to, get the value,
//but not cause an api request
let customer$ = new Rx.ReplaySubject(1);
getCustomer().subscribe(customer$);
//here's the first subscriber
customer$.subscribe(val => console.log('subscriber 1: ' + val));
//here's the second subscriber
setTimeout(() => {
customer$.subscribe(val => console.log('subscriber 2: ' + val));
}, 1000);
function getCustomer() {
return new Rx.Observable(observer => {
console.log('api request');
setTimeout(() => {
console.log('api response');
observer.next('customer object');
observer.complete();
}, 500);
});
}
Here's the proof :)
There is but one takeaway: getCustomer().subscribe(customer$)
We are not subscribing to the api response of getCustomer()
, we are subscribing to a ReplaySubject which is observable which is also able to subscribe to a different Observable and (and this is important) hold it's last emitted value and republish it to any of it's(ReplaySubject's) subscribers.
1
I like this approach as it makes good use of rxjs and no need to add custom logic, thank-you
– Thibs
Jan 25 '17 at 1:47
add a comment |
I starred the question, but i'll try and have a go at this.
//this will be the shared observable that
//anyone can subscribe to, get the value,
//but not cause an api request
let customer$ = new Rx.ReplaySubject(1);
getCustomer().subscribe(customer$);
//here's the first subscriber
customer$.subscribe(val => console.log('subscriber 1: ' + val));
//here's the second subscriber
setTimeout(() => {
customer$.subscribe(val => console.log('subscriber 2: ' + val));
}, 1000);
function getCustomer() {
return new Rx.Observable(observer => {
console.log('api request');
setTimeout(() => {
console.log('api response');
observer.next('customer object');
observer.complete();
}, 500);
});
}
Here's the proof :)
There is but one takeaway: getCustomer().subscribe(customer$)
We are not subscribing to the api response of getCustomer()
, we are subscribing to a ReplaySubject which is observable which is also able to subscribe to a different Observable and (and this is important) hold it's last emitted value and republish it to any of it's(ReplaySubject's) subscribers.
I starred the question, but i'll try and have a go at this.
//this will be the shared observable that
//anyone can subscribe to, get the value,
//but not cause an api request
let customer$ = new Rx.ReplaySubject(1);
getCustomer().subscribe(customer$);
//here's the first subscriber
customer$.subscribe(val => console.log('subscriber 1: ' + val));
//here's the second subscriber
setTimeout(() => {
customer$.subscribe(val => console.log('subscriber 2: ' + val));
}, 1000);
function getCustomer() {
return new Rx.Observable(observer => {
console.log('api request');
setTimeout(() => {
console.log('api response');
observer.next('customer object');
observer.complete();
}, 500);
});
}
Here's the proof :)
There is but one takeaway: getCustomer().subscribe(customer$)
We are not subscribing to the api response of getCustomer()
, we are subscribing to a ReplaySubject which is observable which is also able to subscribe to a different Observable and (and this is important) hold it's last emitted value and republish it to any of it's(ReplaySubject's) subscribers.
edited Jan 21 '18 at 6:42
German Lashevich
809920
809920
answered Mar 29 '16 at 21:59


Birowsky
2,39852154
2,39852154
1
I like this approach as it makes good use of rxjs and no need to add custom logic, thank-you
– Thibs
Jan 25 '17 at 1:47
add a comment |
1
I like this approach as it makes good use of rxjs and no need to add custom logic, thank-you
– Thibs
Jan 25 '17 at 1:47
1
1
I like this approach as it makes good use of rxjs and no need to add custom logic, thank-you
– Thibs
Jan 25 '17 at 1:47
I like this approach as it makes good use of rxjs and no need to add custom logic, thank-you
– Thibs
Jan 25 '17 at 1:47
add a comment |
The implementation you choose is going to depend on if you want unsubscribe() to cancel your HTTP request or not.
In any case, TypeScript decorators are a nice way of standardizing behavior. This is the one I wrote:
@CacheObservableArgsKey
getMyThing(id: string): Observable<any> {
return this.http.get('things/'+id);
}
Decorator definition:
/**
* Decorator that replays and connects to the Observable returned from the function.
* Caches the result using all arguments to form a key.
* @param target
* @param name
* @param descriptor
* @returns {PropertyDescriptor}
*/
export function CacheObservableArgsKey(target: Object, name: string, descriptor: PropertyDescriptor) {
const originalFunc = descriptor.value;
const cacheMap = new Map<string, any>();
descriptor.value = function(this: any, ...args: any): any {
const key = args.join('::');
let returnValue = cacheMap.get(key);
if (returnValue !== undefined) {
console.log(`${name} cache-hit ${key}`, returnValue);
return returnValue;
}
returnValue = originalFunc.apply(this, args);
console.log(`${name} cache-miss ${key} new`, returnValue);
if (returnValue instanceof Observable) {
returnValue = returnValue.publishReplay(1);
returnValue.connect();
}
else {
console.warn('CacheHttpArgsKey: value not an Observable cannot publishReplay and connect', returnValue);
}
cacheMap.set(key, returnValue);
return returnValue;
};
return descriptor;
}
Hi @Arlo - the example above does not compile.Property 'connect' does not exist on type '{}'.
from the linereturnValue.connect();
. Can you elaborate?
– Hoof
Nov 15 '17 at 7:49
add a comment |
The implementation you choose is going to depend on if you want unsubscribe() to cancel your HTTP request or not.
In any case, TypeScript decorators are a nice way of standardizing behavior. This is the one I wrote:
@CacheObservableArgsKey
getMyThing(id: string): Observable<any> {
return this.http.get('things/'+id);
}
Decorator definition:
/**
* Decorator that replays and connects to the Observable returned from the function.
* Caches the result using all arguments to form a key.
* @param target
* @param name
* @param descriptor
* @returns {PropertyDescriptor}
*/
export function CacheObservableArgsKey(target: Object, name: string, descriptor: PropertyDescriptor) {
const originalFunc = descriptor.value;
const cacheMap = new Map<string, any>();
descriptor.value = function(this: any, ...args: any): any {
const key = args.join('::');
let returnValue = cacheMap.get(key);
if (returnValue !== undefined) {
console.log(`${name} cache-hit ${key}`, returnValue);
return returnValue;
}
returnValue = originalFunc.apply(this, args);
console.log(`${name} cache-miss ${key} new`, returnValue);
if (returnValue instanceof Observable) {
returnValue = returnValue.publishReplay(1);
returnValue.connect();
}
else {
console.warn('CacheHttpArgsKey: value not an Observable cannot publishReplay and connect', returnValue);
}
cacheMap.set(key, returnValue);
return returnValue;
};
return descriptor;
}
Hi @Arlo - the example above does not compile.Property 'connect' does not exist on type '{}'.
from the linereturnValue.connect();
. Can you elaborate?
– Hoof
Nov 15 '17 at 7:49
add a comment |
The implementation you choose is going to depend on if you want unsubscribe() to cancel your HTTP request or not.
In any case, TypeScript decorators are a nice way of standardizing behavior. This is the one I wrote:
@CacheObservableArgsKey
getMyThing(id: string): Observable<any> {
return this.http.get('things/'+id);
}
Decorator definition:
/**
* Decorator that replays and connects to the Observable returned from the function.
* Caches the result using all arguments to form a key.
* @param target
* @param name
* @param descriptor
* @returns {PropertyDescriptor}
*/
export function CacheObservableArgsKey(target: Object, name: string, descriptor: PropertyDescriptor) {
const originalFunc = descriptor.value;
const cacheMap = new Map<string, any>();
descriptor.value = function(this: any, ...args: any): any {
const key = args.join('::');
let returnValue = cacheMap.get(key);
if (returnValue !== undefined) {
console.log(`${name} cache-hit ${key}`, returnValue);
return returnValue;
}
returnValue = originalFunc.apply(this, args);
console.log(`${name} cache-miss ${key} new`, returnValue);
if (returnValue instanceof Observable) {
returnValue = returnValue.publishReplay(1);
returnValue.connect();
}
else {
console.warn('CacheHttpArgsKey: value not an Observable cannot publishReplay and connect', returnValue);
}
cacheMap.set(key, returnValue);
return returnValue;
};
return descriptor;
}
The implementation you choose is going to depend on if you want unsubscribe() to cancel your HTTP request or not.
In any case, TypeScript decorators are a nice way of standardizing behavior. This is the one I wrote:
@CacheObservableArgsKey
getMyThing(id: string): Observable<any> {
return this.http.get('things/'+id);
}
Decorator definition:
/**
* Decorator that replays and connects to the Observable returned from the function.
* Caches the result using all arguments to form a key.
* @param target
* @param name
* @param descriptor
* @returns {PropertyDescriptor}
*/
export function CacheObservableArgsKey(target: Object, name: string, descriptor: PropertyDescriptor) {
const originalFunc = descriptor.value;
const cacheMap = new Map<string, any>();
descriptor.value = function(this: any, ...args: any): any {
const key = args.join('::');
let returnValue = cacheMap.get(key);
if (returnValue !== undefined) {
console.log(`${name} cache-hit ${key}`, returnValue);
return returnValue;
}
returnValue = originalFunc.apply(this, args);
console.log(`${name} cache-miss ${key} new`, returnValue);
if (returnValue instanceof Observable) {
returnValue = returnValue.publishReplay(1);
returnValue.connect();
}
else {
console.warn('CacheHttpArgsKey: value not an Observable cannot publishReplay and connect', returnValue);
}
cacheMap.set(key, returnValue);
return returnValue;
};
return descriptor;
}
edited Jan 20 '18 at 23:17
German Lashevich
809920
809920
answered May 9 '17 at 20:38


Arlo
7381818
7381818
Hi @Arlo - the example above does not compile.Property 'connect' does not exist on type '{}'.
from the linereturnValue.connect();
. Can you elaborate?
– Hoof
Nov 15 '17 at 7:49
add a comment |
Hi @Arlo - the example above does not compile.Property 'connect' does not exist on type '{}'.
from the linereturnValue.connect();
. Can you elaborate?
– Hoof
Nov 15 '17 at 7:49
Hi @Arlo - the example above does not compile.
Property 'connect' does not exist on type '{}'.
from the line returnValue.connect();
. Can you elaborate?– Hoof
Nov 15 '17 at 7:49
Hi @Arlo - the example above does not compile.
Property 'connect' does not exist on type '{}'.
from the line returnValue.connect();
. Can you elaborate?– Hoof
Nov 15 '17 at 7:49
add a comment |
I found a way to store the http get result into sessionStorage and use it for the session, so that it will never call the server again.
I used it to call github API to avoid usage limit.
@Injectable()
export class HttpCache {
constructor(private http: Http) {}
get(url: string): Observable<any> {
let cached: any;
if (cached === sessionStorage.getItem(url)) {
return Observable.of(JSON.parse(cached));
} else {
return this.http.get(url)
.map(resp => {
sessionStorage.setItem(url, resp.text());
return resp.json();
});
}
}
}
FYI, sessionStorage limit is 5M(or 4.75M). So, it should not be used like this for large set of data.
------ edit -------------
If you want to have refreshed data with F5, which usesmemory data instead of sessionStorage;
@Injectable()
export class HttpCache {
cached: any = {}; // this will store data
constructor(private http: Http) {}
get(url: string): Observable<any> {
if (this.cached[url]) {
return Observable.of(this.cached[url]));
} else {
return this.http.get(url)
.map(resp => {
this.cached[url] = resp.text();
return resp.json();
});
}
}
}
If you will store in session Storage then How will you make sure that Session storage is destroyed when you leave the app ?
– Gags
Jul 30 '17 at 13:04
@Gags sessionStorage.clear();
– Cassiano Montanari
Mar 23 '18 at 12:24
but this introduces unexpected behavior for the user. When the user hits F5 or refresh button of the browser, then he expects fresh data from server. But actually he is getting outdated data from localStorage. Bug reports, support tickets, etc. incoming... As the namesessionStorage
says, I would use it only for data that is expected to be consistent for the whole session.
– MA-Maddin
Mar 31 '18 at 22:28
@MA-Maddin as I stated "I used it to avoid usage limit". If you want want data to be refreshed with F5, you need to use memory instead of sessionStorage. The answer has been edited with this approach.
– allenhwkim
Mar 31 '18 at 23:53
yep, that might be a use case. I just got triggered since everyone is talking about Cache and OP hasgetCustomer
in his example. ;) So just wanted to warn some ppl that might do not see the risks :)
– MA-Maddin
Mar 31 '18 at 23:57
add a comment |
I found a way to store the http get result into sessionStorage and use it for the session, so that it will never call the server again.
I used it to call github API to avoid usage limit.
@Injectable()
export class HttpCache {
constructor(private http: Http) {}
get(url: string): Observable<any> {
let cached: any;
if (cached === sessionStorage.getItem(url)) {
return Observable.of(JSON.parse(cached));
} else {
return this.http.get(url)
.map(resp => {
sessionStorage.setItem(url, resp.text());
return resp.json();
});
}
}
}
FYI, sessionStorage limit is 5M(or 4.75M). So, it should not be used like this for large set of data.
------ edit -------------
If you want to have refreshed data with F5, which usesmemory data instead of sessionStorage;
@Injectable()
export class HttpCache {
cached: any = {}; // this will store data
constructor(private http: Http) {}
get(url: string): Observable<any> {
if (this.cached[url]) {
return Observable.of(this.cached[url]));
} else {
return this.http.get(url)
.map(resp => {
this.cached[url] = resp.text();
return resp.json();
});
}
}
}
If you will store in session Storage then How will you make sure that Session storage is destroyed when you leave the app ?
– Gags
Jul 30 '17 at 13:04
@Gags sessionStorage.clear();
– Cassiano Montanari
Mar 23 '18 at 12:24
but this introduces unexpected behavior for the user. When the user hits F5 or refresh button of the browser, then he expects fresh data from server. But actually he is getting outdated data from localStorage. Bug reports, support tickets, etc. incoming... As the namesessionStorage
says, I would use it only for data that is expected to be consistent for the whole session.
– MA-Maddin
Mar 31 '18 at 22:28
@MA-Maddin as I stated "I used it to avoid usage limit". If you want want data to be refreshed with F5, you need to use memory instead of sessionStorage. The answer has been edited with this approach.
– allenhwkim
Mar 31 '18 at 23:53
yep, that might be a use case. I just got triggered since everyone is talking about Cache and OP hasgetCustomer
in his example. ;) So just wanted to warn some ppl that might do not see the risks :)
– MA-Maddin
Mar 31 '18 at 23:57
add a comment |
I found a way to store the http get result into sessionStorage and use it for the session, so that it will never call the server again.
I used it to call github API to avoid usage limit.
@Injectable()
export class HttpCache {
constructor(private http: Http) {}
get(url: string): Observable<any> {
let cached: any;
if (cached === sessionStorage.getItem(url)) {
return Observable.of(JSON.parse(cached));
} else {
return this.http.get(url)
.map(resp => {
sessionStorage.setItem(url, resp.text());
return resp.json();
});
}
}
}
FYI, sessionStorage limit is 5M(or 4.75M). So, it should not be used like this for large set of data.
------ edit -------------
If you want to have refreshed data with F5, which usesmemory data instead of sessionStorage;
@Injectable()
export class HttpCache {
cached: any = {}; // this will store data
constructor(private http: Http) {}
get(url: string): Observable<any> {
if (this.cached[url]) {
return Observable.of(this.cached[url]));
} else {
return this.http.get(url)
.map(resp => {
this.cached[url] = resp.text();
return resp.json();
});
}
}
}
I found a way to store the http get result into sessionStorage and use it for the session, so that it will never call the server again.
I used it to call github API to avoid usage limit.
@Injectable()
export class HttpCache {
constructor(private http: Http) {}
get(url: string): Observable<any> {
let cached: any;
if (cached === sessionStorage.getItem(url)) {
return Observable.of(JSON.parse(cached));
} else {
return this.http.get(url)
.map(resp => {
sessionStorage.setItem(url, resp.text());
return resp.json();
});
}
}
}
FYI, sessionStorage limit is 5M(or 4.75M). So, it should not be used like this for large set of data.
------ edit -------------
If you want to have refreshed data with F5, which usesmemory data instead of sessionStorage;
@Injectable()
export class HttpCache {
cached: any = {}; // this will store data
constructor(private http: Http) {}
get(url: string): Observable<any> {
if (this.cached[url]) {
return Observable.of(this.cached[url]));
} else {
return this.http.get(url)
.map(resp => {
this.cached[url] = resp.text();
return resp.json();
});
}
}
}
edited Mar 31 '18 at 23:53
answered Jul 26 '16 at 22:25


allenhwkim
20.6k1264102
20.6k1264102
If you will store in session Storage then How will you make sure that Session storage is destroyed when you leave the app ?
– Gags
Jul 30 '17 at 13:04
@Gags sessionStorage.clear();
– Cassiano Montanari
Mar 23 '18 at 12:24
but this introduces unexpected behavior for the user. When the user hits F5 or refresh button of the browser, then he expects fresh data from server. But actually he is getting outdated data from localStorage. Bug reports, support tickets, etc. incoming... As the namesessionStorage
says, I would use it only for data that is expected to be consistent for the whole session.
– MA-Maddin
Mar 31 '18 at 22:28
@MA-Maddin as I stated "I used it to avoid usage limit". If you want want data to be refreshed with F5, you need to use memory instead of sessionStorage. The answer has been edited with this approach.
– allenhwkim
Mar 31 '18 at 23:53
yep, that might be a use case. I just got triggered since everyone is talking about Cache and OP hasgetCustomer
in his example. ;) So just wanted to warn some ppl that might do not see the risks :)
– MA-Maddin
Mar 31 '18 at 23:57
add a comment |
If you will store in session Storage then How will you make sure that Session storage is destroyed when you leave the app ?
– Gags
Jul 30 '17 at 13:04
@Gags sessionStorage.clear();
– Cassiano Montanari
Mar 23 '18 at 12:24
but this introduces unexpected behavior for the user. When the user hits F5 or refresh button of the browser, then he expects fresh data from server. But actually he is getting outdated data from localStorage. Bug reports, support tickets, etc. incoming... As the namesessionStorage
says, I would use it only for data that is expected to be consistent for the whole session.
– MA-Maddin
Mar 31 '18 at 22:28
@MA-Maddin as I stated "I used it to avoid usage limit". If you want want data to be refreshed with F5, you need to use memory instead of sessionStorage. The answer has been edited with this approach.
– allenhwkim
Mar 31 '18 at 23:53
yep, that might be a use case. I just got triggered since everyone is talking about Cache and OP hasgetCustomer
in his example. ;) So just wanted to warn some ppl that might do not see the risks :)
– MA-Maddin
Mar 31 '18 at 23:57
If you will store in session Storage then How will you make sure that Session storage is destroyed when you leave the app ?
– Gags
Jul 30 '17 at 13:04
If you will store in session Storage then How will you make sure that Session storage is destroyed when you leave the app ?
– Gags
Jul 30 '17 at 13:04
@Gags sessionStorage.clear();
– Cassiano Montanari
Mar 23 '18 at 12:24
@Gags sessionStorage.clear();
– Cassiano Montanari
Mar 23 '18 at 12:24
but this introduces unexpected behavior for the user. When the user hits F5 or refresh button of the browser, then he expects fresh data from server. But actually he is getting outdated data from localStorage. Bug reports, support tickets, etc. incoming... As the name
sessionStorage
says, I would use it only for data that is expected to be consistent for the whole session.– MA-Maddin
Mar 31 '18 at 22:28
but this introduces unexpected behavior for the user. When the user hits F5 or refresh button of the browser, then he expects fresh data from server. But actually he is getting outdated data from localStorage. Bug reports, support tickets, etc. incoming... As the name
sessionStorage
says, I would use it only for data that is expected to be consistent for the whole session.– MA-Maddin
Mar 31 '18 at 22:28
@MA-Maddin as I stated "I used it to avoid usage limit". If you want want data to be refreshed with F5, you need to use memory instead of sessionStorage. The answer has been edited with this approach.
– allenhwkim
Mar 31 '18 at 23:53
@MA-Maddin as I stated "I used it to avoid usage limit". If you want want data to be refreshed with F5, you need to use memory instead of sessionStorage. The answer has been edited with this approach.
– allenhwkim
Mar 31 '18 at 23:53
yep, that might be a use case. I just got triggered since everyone is talking about Cache and OP has
getCustomer
in his example. ;) So just wanted to warn some ppl that might do not see the risks :)– MA-Maddin
Mar 31 '18 at 23:57
yep, that might be a use case. I just got triggered since everyone is talking about Cache and OP has
getCustomer
in his example. ;) So just wanted to warn some ppl that might do not see the risks :)– MA-Maddin
Mar 31 '18 at 23:57
add a comment |
Cacheable HTTP Response Data using Rxjs Observer/Observable + Caching + Subscription
See Code Below
*disclaimer: I am new to rxjs, so bear in mind that I may be misusing the observable/observer approach. My solution is purely a conglomeration of other solutions I found, and is the consequence of having failed to find a simple well-documented solution. Thus I am providing my complete code solution (as I would liked to have found) in hopes that it helps others.
*note, this approach is loosely based on GoogleFirebaseObservables. Unfortunately I lack the proper experience/time to replicate what they did under the hood. But the following is a simplistic way of providing asynchronous access to some cache-able data.
Situation: A 'product-list' component is tasked with displaying a list of products. The site is a single-page web app with some menu buttons that will 'filter' the products displayed on the page.
Solution: The component "subscribes" to a service method. The service method returns an array of product objects, which the component accesses through the subscription callback. The service method wraps its activity in a newly created Observer and returns the observer. Inside this observer, it searches for cached data and passes it back to the subscriber (the component) and returns. Otherwise it issues an http call to retrieve the data, subscribes to the response, where you can process that data (e.g. map the data to your own model) and then pass the data back to the subscriber.
The Code
product-list.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { ProductService } from '../../../services/product.service';
import { Product, ProductResponse } from '../../../models/Product';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
products: Product;
constructor(
private productService: ProductService
) { }
ngOnInit() {
console.log('product-list init...');
this.productService.getProducts().subscribe(products => {
console.log('product-list received updated products');
this.products = products;
});
}
}
product.service.ts
import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { Observable, Observer } from 'rxjs';
import 'rxjs/add/operator/map';
import { Product, ProductResponse } from '../models/Product';
@Injectable()
export class ProductService {
products: Product;
constructor(
private http:Http
) {
console.log('product service init. calling http to get products...');
}
getProducts():Observable<Product>{
//wrap getProducts around an Observable to make it async.
let productsObservable$ = Observable.create((observer: Observer<Product>) => {
//return products if it was previously fetched
if(this.products){
console.log('## returning existing products');
observer.next(this.products);
return observer.complete();
}
//Fetch products from REST API
console.log('** products do not yet exist; fetching from rest api...');
let headers = new Headers();
this.http.get('http://localhost:3000/products/', {headers: headers})
.map(res => res.json()).subscribe((response:ProductResponse) => {
console.log('productResponse: ', response);
let productlist = Product.fromJsonList(response.products); //convert service observable to product
this.products = productlist;
observer.next(productlist);
});
});
return productsObservable$;
}
}
product.ts (the model)
export interface ProductResponse {
success: boolean;
msg: string;
products: Product;
}
export class Product {
product_id: number;
sku: string;
product_title: string;
..etc...
constructor(product_id: number,
sku: string,
product_title: string,
...etc...
){
//typescript will not autoassign the formal parameters to related properties for exported classes.
this.product_id = product_id;
this.sku = sku;
this.product_title = product_title;
...etc...
}
//Class method to convert products within http response to pure array of Product objects.
//Caller: product.service:getProducts()
static fromJsonList(products:any): Product {
let mappedArray = products.map(Product.fromJson);
return mappedArray;
}
//add more parameters depending on your database entries and constructor
static fromJson({
product_id,
sku,
product_title,
...etc...
}): Product {
return new Product(
product_id,
sku,
product_title,
...etc...
);
}
}
Here is a sample of the output I see when I load the page in Chrome. Note that on the initial load, the products are fetched from http (call to my node rest service, which is running locally on port 3000). When I then click to navigate to a 'filtered' view of the products, the products are found in cache.
My Chrome Log (console):
core.es5.js:2925 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
app.component.ts:19 app.component url: /products
product.service.ts:15 product service init. calling http to get products...
product-list.component.ts:18 product-list init...
product.service.ts:29 ** products do not yet exist; fetching from rest api...
product.service.ts:33 productResponse: {success: true, msg: "Products found", products: Array(23)}
product-list.component.ts:20 product-list received updated products
...[clicked a menu button to filter the products]...
app.component.ts:19 app.component url: /products/chocolatechip
product-list.component.ts:18 product-list init...
product.service.ts:24 ## returning existing products
product-list.component.ts:20 product-list received updated products
Conclusion: This is the simplest way I've found (so far) to implement cacheable http response data. In my angular app, each time I navigate to a different view of the products, the product-list component reloads. ProductService seems to be a shared instance, so the local cache of 'products: Product' in the ProductService is retained during navigation, and subsequent calls to "GetProducts()" returns the cached value. One final note, I've read comments about how observables/subscriptions need to be closed when you're finished to prevent 'memory leaks'. I've not included this here, but it's something to keep in mind.
1
Note - I've since found a more powerful solution, involving RxJS BehaviorSubjects, which simplifies the code and dramatically cuts down on 'overhead'. In products.service.ts, 1. import { BehaviorSubject } from 'rxjs'; 2. change 'products:Product' into 'product$: BehaviorSubject<Product> = new BehaviorSubject<Product>();' 3. Now you can simply call the http without returning anything. http_getProducts(){this.http.get(...).map(res => res.json()).subscribe(products => this.product$.next(products))};
– ObjectiveTC
Oct 2 '17 at 6:41
1
The local variable 'product$' is a behaviorSubject, which will both EMIT and STORE the latest products (from the product$.next(..) call in part 3). Now in your components, inject the service as normal. You get the most recently assigned value of product$ using productService.product$.value. Or subscribe to product$ if you want to perform an action whenever product$ receives a new value (i.e., the product$.next(...) function is called in part 3).
– ObjectiveTC
Oct 2 '17 at 6:41
1
Eg, in products.component.ts... this.productService.product$ .takeUntil(this.ngUnsubscribe) .subscribe((products) => {this.category); let filteredProducts = this.productService.getProductsByCategory(this.category); this.products = filteredProducts; });
– ObjectiveTC
Oct 2 '17 at 6:41
1
An important note about unsubscribing from observables: ".takeUntil(this.ngUnsubscribe)". See this stack overflow question/answer, which appears to show the 'de-facto' recommended way to unsubscribe from events: stackoverflow.com/questions/38008334/…
– ObjectiveTC
Oct 2 '17 at 6:47
1
The alternative is to the .first() or .take(1) if the observable is only meant to receive data once. All other 'infinite streams' of observables should be unsubscribed in 'ngOnDestroy()', and if you don't then you may end up with duplicate 'observable' callbacks. stackoverflow.com/questions/28007777/…
– ObjectiveTC
Oct 2 '17 at 6:47
add a comment |
Cacheable HTTP Response Data using Rxjs Observer/Observable + Caching + Subscription
See Code Below
*disclaimer: I am new to rxjs, so bear in mind that I may be misusing the observable/observer approach. My solution is purely a conglomeration of other solutions I found, and is the consequence of having failed to find a simple well-documented solution. Thus I am providing my complete code solution (as I would liked to have found) in hopes that it helps others.
*note, this approach is loosely based on GoogleFirebaseObservables. Unfortunately I lack the proper experience/time to replicate what they did under the hood. But the following is a simplistic way of providing asynchronous access to some cache-able data.
Situation: A 'product-list' component is tasked with displaying a list of products. The site is a single-page web app with some menu buttons that will 'filter' the products displayed on the page.
Solution: The component "subscribes" to a service method. The service method returns an array of product objects, which the component accesses through the subscription callback. The service method wraps its activity in a newly created Observer and returns the observer. Inside this observer, it searches for cached data and passes it back to the subscriber (the component) and returns. Otherwise it issues an http call to retrieve the data, subscribes to the response, where you can process that data (e.g. map the data to your own model) and then pass the data back to the subscriber.
The Code
product-list.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { ProductService } from '../../../services/product.service';
import { Product, ProductResponse } from '../../../models/Product';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
products: Product;
constructor(
private productService: ProductService
) { }
ngOnInit() {
console.log('product-list init...');
this.productService.getProducts().subscribe(products => {
console.log('product-list received updated products');
this.products = products;
});
}
}
product.service.ts
import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { Observable, Observer } from 'rxjs';
import 'rxjs/add/operator/map';
import { Product, ProductResponse } from '../models/Product';
@Injectable()
export class ProductService {
products: Product;
constructor(
private http:Http
) {
console.log('product service init. calling http to get products...');
}
getProducts():Observable<Product>{
//wrap getProducts around an Observable to make it async.
let productsObservable$ = Observable.create((observer: Observer<Product>) => {
//return products if it was previously fetched
if(this.products){
console.log('## returning existing products');
observer.next(this.products);
return observer.complete();
}
//Fetch products from REST API
console.log('** products do not yet exist; fetching from rest api...');
let headers = new Headers();
this.http.get('http://localhost:3000/products/', {headers: headers})
.map(res => res.json()).subscribe((response:ProductResponse) => {
console.log('productResponse: ', response);
let productlist = Product.fromJsonList(response.products); //convert service observable to product
this.products = productlist;
observer.next(productlist);
});
});
return productsObservable$;
}
}
product.ts (the model)
export interface ProductResponse {
success: boolean;
msg: string;
products: Product;
}
export class Product {
product_id: number;
sku: string;
product_title: string;
..etc...
constructor(product_id: number,
sku: string,
product_title: string,
...etc...
){
//typescript will not autoassign the formal parameters to related properties for exported classes.
this.product_id = product_id;
this.sku = sku;
this.product_title = product_title;
...etc...
}
//Class method to convert products within http response to pure array of Product objects.
//Caller: product.service:getProducts()
static fromJsonList(products:any): Product {
let mappedArray = products.map(Product.fromJson);
return mappedArray;
}
//add more parameters depending on your database entries and constructor
static fromJson({
product_id,
sku,
product_title,
...etc...
}): Product {
return new Product(
product_id,
sku,
product_title,
...etc...
);
}
}
Here is a sample of the output I see when I load the page in Chrome. Note that on the initial load, the products are fetched from http (call to my node rest service, which is running locally on port 3000). When I then click to navigate to a 'filtered' view of the products, the products are found in cache.
My Chrome Log (console):
core.es5.js:2925 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
app.component.ts:19 app.component url: /products
product.service.ts:15 product service init. calling http to get products...
product-list.component.ts:18 product-list init...
product.service.ts:29 ** products do not yet exist; fetching from rest api...
product.service.ts:33 productResponse: {success: true, msg: "Products found", products: Array(23)}
product-list.component.ts:20 product-list received updated products
...[clicked a menu button to filter the products]...
app.component.ts:19 app.component url: /products/chocolatechip
product-list.component.ts:18 product-list init...
product.service.ts:24 ## returning existing products
product-list.component.ts:20 product-list received updated products
Conclusion: This is the simplest way I've found (so far) to implement cacheable http response data. In my angular app, each time I navigate to a different view of the products, the product-list component reloads. ProductService seems to be a shared instance, so the local cache of 'products: Product' in the ProductService is retained during navigation, and subsequent calls to "GetProducts()" returns the cached value. One final note, I've read comments about how observables/subscriptions need to be closed when you're finished to prevent 'memory leaks'. I've not included this here, but it's something to keep in mind.
1
Note - I've since found a more powerful solution, involving RxJS BehaviorSubjects, which simplifies the code and dramatically cuts down on 'overhead'. In products.service.ts, 1. import { BehaviorSubject } from 'rxjs'; 2. change 'products:Product' into 'product$: BehaviorSubject<Product> = new BehaviorSubject<Product>();' 3. Now you can simply call the http without returning anything. http_getProducts(){this.http.get(...).map(res => res.json()).subscribe(products => this.product$.next(products))};
– ObjectiveTC
Oct 2 '17 at 6:41
1
The local variable 'product$' is a behaviorSubject, which will both EMIT and STORE the latest products (from the product$.next(..) call in part 3). Now in your components, inject the service as normal. You get the most recently assigned value of product$ using productService.product$.value. Or subscribe to product$ if you want to perform an action whenever product$ receives a new value (i.e., the product$.next(...) function is called in part 3).
– ObjectiveTC
Oct 2 '17 at 6:41
1
Eg, in products.component.ts... this.productService.product$ .takeUntil(this.ngUnsubscribe) .subscribe((products) => {this.category); let filteredProducts = this.productService.getProductsByCategory(this.category); this.products = filteredProducts; });
– ObjectiveTC
Oct 2 '17 at 6:41
1
An important note about unsubscribing from observables: ".takeUntil(this.ngUnsubscribe)". See this stack overflow question/answer, which appears to show the 'de-facto' recommended way to unsubscribe from events: stackoverflow.com/questions/38008334/…
– ObjectiveTC
Oct 2 '17 at 6:47
1
The alternative is to the .first() or .take(1) if the observable is only meant to receive data once. All other 'infinite streams' of observables should be unsubscribed in 'ngOnDestroy()', and if you don't then you may end up with duplicate 'observable' callbacks. stackoverflow.com/questions/28007777/…
– ObjectiveTC
Oct 2 '17 at 6:47
add a comment |
Cacheable HTTP Response Data using Rxjs Observer/Observable + Caching + Subscription
See Code Below
*disclaimer: I am new to rxjs, so bear in mind that I may be misusing the observable/observer approach. My solution is purely a conglomeration of other solutions I found, and is the consequence of having failed to find a simple well-documented solution. Thus I am providing my complete code solution (as I would liked to have found) in hopes that it helps others.
*note, this approach is loosely based on GoogleFirebaseObservables. Unfortunately I lack the proper experience/time to replicate what they did under the hood. But the following is a simplistic way of providing asynchronous access to some cache-able data.
Situation: A 'product-list' component is tasked with displaying a list of products. The site is a single-page web app with some menu buttons that will 'filter' the products displayed on the page.
Solution: The component "subscribes" to a service method. The service method returns an array of product objects, which the component accesses through the subscription callback. The service method wraps its activity in a newly created Observer and returns the observer. Inside this observer, it searches for cached data and passes it back to the subscriber (the component) and returns. Otherwise it issues an http call to retrieve the data, subscribes to the response, where you can process that data (e.g. map the data to your own model) and then pass the data back to the subscriber.
The Code
product-list.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { ProductService } from '../../../services/product.service';
import { Product, ProductResponse } from '../../../models/Product';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
products: Product;
constructor(
private productService: ProductService
) { }
ngOnInit() {
console.log('product-list init...');
this.productService.getProducts().subscribe(products => {
console.log('product-list received updated products');
this.products = products;
});
}
}
product.service.ts
import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { Observable, Observer } from 'rxjs';
import 'rxjs/add/operator/map';
import { Product, ProductResponse } from '../models/Product';
@Injectable()
export class ProductService {
products: Product;
constructor(
private http:Http
) {
console.log('product service init. calling http to get products...');
}
getProducts():Observable<Product>{
//wrap getProducts around an Observable to make it async.
let productsObservable$ = Observable.create((observer: Observer<Product>) => {
//return products if it was previously fetched
if(this.products){
console.log('## returning existing products');
observer.next(this.products);
return observer.complete();
}
//Fetch products from REST API
console.log('** products do not yet exist; fetching from rest api...');
let headers = new Headers();
this.http.get('http://localhost:3000/products/', {headers: headers})
.map(res => res.json()).subscribe((response:ProductResponse) => {
console.log('productResponse: ', response);
let productlist = Product.fromJsonList(response.products); //convert service observable to product
this.products = productlist;
observer.next(productlist);
});
});
return productsObservable$;
}
}
product.ts (the model)
export interface ProductResponse {
success: boolean;
msg: string;
products: Product;
}
export class Product {
product_id: number;
sku: string;
product_title: string;
..etc...
constructor(product_id: number,
sku: string,
product_title: string,
...etc...
){
//typescript will not autoassign the formal parameters to related properties for exported classes.
this.product_id = product_id;
this.sku = sku;
this.product_title = product_title;
...etc...
}
//Class method to convert products within http response to pure array of Product objects.
//Caller: product.service:getProducts()
static fromJsonList(products:any): Product {
let mappedArray = products.map(Product.fromJson);
return mappedArray;
}
//add more parameters depending on your database entries and constructor
static fromJson({
product_id,
sku,
product_title,
...etc...
}): Product {
return new Product(
product_id,
sku,
product_title,
...etc...
);
}
}
Here is a sample of the output I see when I load the page in Chrome. Note that on the initial load, the products are fetched from http (call to my node rest service, which is running locally on port 3000). When I then click to navigate to a 'filtered' view of the products, the products are found in cache.
My Chrome Log (console):
core.es5.js:2925 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
app.component.ts:19 app.component url: /products
product.service.ts:15 product service init. calling http to get products...
product-list.component.ts:18 product-list init...
product.service.ts:29 ** products do not yet exist; fetching from rest api...
product.service.ts:33 productResponse: {success: true, msg: "Products found", products: Array(23)}
product-list.component.ts:20 product-list received updated products
...[clicked a menu button to filter the products]...
app.component.ts:19 app.component url: /products/chocolatechip
product-list.component.ts:18 product-list init...
product.service.ts:24 ## returning existing products
product-list.component.ts:20 product-list received updated products
Conclusion: This is the simplest way I've found (so far) to implement cacheable http response data. In my angular app, each time I navigate to a different view of the products, the product-list component reloads. ProductService seems to be a shared instance, so the local cache of 'products: Product' in the ProductService is retained during navigation, and subsequent calls to "GetProducts()" returns the cached value. One final note, I've read comments about how observables/subscriptions need to be closed when you're finished to prevent 'memory leaks'. I've not included this here, but it's something to keep in mind.
Cacheable HTTP Response Data using Rxjs Observer/Observable + Caching + Subscription
See Code Below
*disclaimer: I am new to rxjs, so bear in mind that I may be misusing the observable/observer approach. My solution is purely a conglomeration of other solutions I found, and is the consequence of having failed to find a simple well-documented solution. Thus I am providing my complete code solution (as I would liked to have found) in hopes that it helps others.
*note, this approach is loosely based on GoogleFirebaseObservables. Unfortunately I lack the proper experience/time to replicate what they did under the hood. But the following is a simplistic way of providing asynchronous access to some cache-able data.
Situation: A 'product-list' component is tasked with displaying a list of products. The site is a single-page web app with some menu buttons that will 'filter' the products displayed on the page.
Solution: The component "subscribes" to a service method. The service method returns an array of product objects, which the component accesses through the subscription callback. The service method wraps its activity in a newly created Observer and returns the observer. Inside this observer, it searches for cached data and passes it back to the subscriber (the component) and returns. Otherwise it issues an http call to retrieve the data, subscribes to the response, where you can process that data (e.g. map the data to your own model) and then pass the data back to the subscriber.
The Code
product-list.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { ProductService } from '../../../services/product.service';
import { Product, ProductResponse } from '../../../models/Product';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
products: Product;
constructor(
private productService: ProductService
) { }
ngOnInit() {
console.log('product-list init...');
this.productService.getProducts().subscribe(products => {
console.log('product-list received updated products');
this.products = products;
});
}
}
product.service.ts
import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { Observable, Observer } from 'rxjs';
import 'rxjs/add/operator/map';
import { Product, ProductResponse } from '../models/Product';
@Injectable()
export class ProductService {
products: Product;
constructor(
private http:Http
) {
console.log('product service init. calling http to get products...');
}
getProducts():Observable<Product>{
//wrap getProducts around an Observable to make it async.
let productsObservable$ = Observable.create((observer: Observer<Product>) => {
//return products if it was previously fetched
if(this.products){
console.log('## returning existing products');
observer.next(this.products);
return observer.complete();
}
//Fetch products from REST API
console.log('** products do not yet exist; fetching from rest api...');
let headers = new Headers();
this.http.get('http://localhost:3000/products/', {headers: headers})
.map(res => res.json()).subscribe((response:ProductResponse) => {
console.log('productResponse: ', response);
let productlist = Product.fromJsonList(response.products); //convert service observable to product
this.products = productlist;
observer.next(productlist);
});
});
return productsObservable$;
}
}
product.ts (the model)
export interface ProductResponse {
success: boolean;
msg: string;
products: Product;
}
export class Product {
product_id: number;
sku: string;
product_title: string;
..etc...
constructor(product_id: number,
sku: string,
product_title: string,
...etc...
){
//typescript will not autoassign the formal parameters to related properties for exported classes.
this.product_id = product_id;
this.sku = sku;
this.product_title = product_title;
...etc...
}
//Class method to convert products within http response to pure array of Product objects.
//Caller: product.service:getProducts()
static fromJsonList(products:any): Product {
let mappedArray = products.map(Product.fromJson);
return mappedArray;
}
//add more parameters depending on your database entries and constructor
static fromJson({
product_id,
sku,
product_title,
...etc...
}): Product {
return new Product(
product_id,
sku,
product_title,
...etc...
);
}
}
Here is a sample of the output I see when I load the page in Chrome. Note that on the initial load, the products are fetched from http (call to my node rest service, which is running locally on port 3000). When I then click to navigate to a 'filtered' view of the products, the products are found in cache.
My Chrome Log (console):
core.es5.js:2925 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
app.component.ts:19 app.component url: /products
product.service.ts:15 product service init. calling http to get products...
product-list.component.ts:18 product-list init...
product.service.ts:29 ** products do not yet exist; fetching from rest api...
product.service.ts:33 productResponse: {success: true, msg: "Products found", products: Array(23)}
product-list.component.ts:20 product-list received updated products
...[clicked a menu button to filter the products]...
app.component.ts:19 app.component url: /products/chocolatechip
product-list.component.ts:18 product-list init...
product.service.ts:24 ## returning existing products
product-list.component.ts:20 product-list received updated products
Conclusion: This is the simplest way I've found (so far) to implement cacheable http response data. In my angular app, each time I navigate to a different view of the products, the product-list component reloads. ProductService seems to be a shared instance, so the local cache of 'products: Product' in the ProductService is retained during navigation, and subsequent calls to "GetProducts()" returns the cached value. One final note, I've read comments about how observables/subscriptions need to be closed when you're finished to prevent 'memory leaks'. I've not included this here, but it's something to keep in mind.
edited Sep 2 '17 at 8:04
answered Sep 2 '17 at 7:46
ObjectiveTC
1,5932218
1,5932218
1
Note - I've since found a more powerful solution, involving RxJS BehaviorSubjects, which simplifies the code and dramatically cuts down on 'overhead'. In products.service.ts, 1. import { BehaviorSubject } from 'rxjs'; 2. change 'products:Product' into 'product$: BehaviorSubject<Product> = new BehaviorSubject<Product>();' 3. Now you can simply call the http without returning anything. http_getProducts(){this.http.get(...).map(res => res.json()).subscribe(products => this.product$.next(products))};
– ObjectiveTC
Oct 2 '17 at 6:41
1
The local variable 'product$' is a behaviorSubject, which will both EMIT and STORE the latest products (from the product$.next(..) call in part 3). Now in your components, inject the service as normal. You get the most recently assigned value of product$ using productService.product$.value. Or subscribe to product$ if you want to perform an action whenever product$ receives a new value (i.e., the product$.next(...) function is called in part 3).
– ObjectiveTC
Oct 2 '17 at 6:41
1
Eg, in products.component.ts... this.productService.product$ .takeUntil(this.ngUnsubscribe) .subscribe((products) => {this.category); let filteredProducts = this.productService.getProductsByCategory(this.category); this.products = filteredProducts; });
– ObjectiveTC
Oct 2 '17 at 6:41
1
An important note about unsubscribing from observables: ".takeUntil(this.ngUnsubscribe)". See this stack overflow question/answer, which appears to show the 'de-facto' recommended way to unsubscribe from events: stackoverflow.com/questions/38008334/…
– ObjectiveTC
Oct 2 '17 at 6:47
1
The alternative is to the .first() or .take(1) if the observable is only meant to receive data once. All other 'infinite streams' of observables should be unsubscribed in 'ngOnDestroy()', and if you don't then you may end up with duplicate 'observable' callbacks. stackoverflow.com/questions/28007777/…
– ObjectiveTC
Oct 2 '17 at 6:47
add a comment |
1
Note - I've since found a more powerful solution, involving RxJS BehaviorSubjects, which simplifies the code and dramatically cuts down on 'overhead'. In products.service.ts, 1. import { BehaviorSubject } from 'rxjs'; 2. change 'products:Product' into 'product$: BehaviorSubject<Product> = new BehaviorSubject<Product>();' 3. Now you can simply call the http without returning anything. http_getProducts(){this.http.get(...).map(res => res.json()).subscribe(products => this.product$.next(products))};
– ObjectiveTC
Oct 2 '17 at 6:41
1
The local variable 'product$' is a behaviorSubject, which will both EMIT and STORE the latest products (from the product$.next(..) call in part 3). Now in your components, inject the service as normal. You get the most recently assigned value of product$ using productService.product$.value. Or subscribe to product$ if you want to perform an action whenever product$ receives a new value (i.e., the product$.next(...) function is called in part 3).
– ObjectiveTC
Oct 2 '17 at 6:41
1
Eg, in products.component.ts... this.productService.product$ .takeUntil(this.ngUnsubscribe) .subscribe((products) => {this.category); let filteredProducts = this.productService.getProductsByCategory(this.category); this.products = filteredProducts; });
– ObjectiveTC
Oct 2 '17 at 6:41
1
An important note about unsubscribing from observables: ".takeUntil(this.ngUnsubscribe)". See this stack overflow question/answer, which appears to show the 'de-facto' recommended way to unsubscribe from events: stackoverflow.com/questions/38008334/…
– ObjectiveTC
Oct 2 '17 at 6:47
1
The alternative is to the .first() or .take(1) if the observable is only meant to receive data once. All other 'infinite streams' of observables should be unsubscribed in 'ngOnDestroy()', and if you don't then you may end up with duplicate 'observable' callbacks. stackoverflow.com/questions/28007777/…
– ObjectiveTC
Oct 2 '17 at 6:47
1
1
Note - I've since found a more powerful solution, involving RxJS BehaviorSubjects, which simplifies the code and dramatically cuts down on 'overhead'. In products.service.ts, 1. import { BehaviorSubject } from 'rxjs'; 2. change 'products:Product' into 'product$: BehaviorSubject<Product> = new BehaviorSubject<Product>();' 3. Now you can simply call the http without returning anything. http_getProducts(){this.http.get(...).map(res => res.json()).subscribe(products => this.product$.next(products))};
– ObjectiveTC
Oct 2 '17 at 6:41
Note - I've since found a more powerful solution, involving RxJS BehaviorSubjects, which simplifies the code and dramatically cuts down on 'overhead'. In products.service.ts, 1. import { BehaviorSubject } from 'rxjs'; 2. change 'products:Product' into 'product$: BehaviorSubject<Product> = new BehaviorSubject<Product>();' 3. Now you can simply call the http without returning anything. http_getProducts(){this.http.get(...).map(res => res.json()).subscribe(products => this.product$.next(products))};
– ObjectiveTC
Oct 2 '17 at 6:41
1
1
The local variable 'product$' is a behaviorSubject, which will both EMIT and STORE the latest products (from the product$.next(..) call in part 3). Now in your components, inject the service as normal. You get the most recently assigned value of product$ using productService.product$.value. Or subscribe to product$ if you want to perform an action whenever product$ receives a new value (i.e., the product$.next(...) function is called in part 3).
– ObjectiveTC
Oct 2 '17 at 6:41
The local variable 'product$' is a behaviorSubject, which will both EMIT and STORE the latest products (from the product$.next(..) call in part 3). Now in your components, inject the service as normal. You get the most recently assigned value of product$ using productService.product$.value. Or subscribe to product$ if you want to perform an action whenever product$ receives a new value (i.e., the product$.next(...) function is called in part 3).
– ObjectiveTC
Oct 2 '17 at 6:41
1
1
Eg, in products.component.ts... this.productService.product$ .takeUntil(this.ngUnsubscribe) .subscribe((products) => {this.category); let filteredProducts = this.productService.getProductsByCategory(this.category); this.products = filteredProducts; });
– ObjectiveTC
Oct 2 '17 at 6:41
Eg, in products.component.ts... this.productService.product$ .takeUntil(this.ngUnsubscribe) .subscribe((products) => {this.category); let filteredProducts = this.productService.getProductsByCategory(this.category); this.products = filteredProducts; });
– ObjectiveTC
Oct 2 '17 at 6:41
1
1
An important note about unsubscribing from observables: ".takeUntil(this.ngUnsubscribe)". See this stack overflow question/answer, which appears to show the 'de-facto' recommended way to unsubscribe from events: stackoverflow.com/questions/38008334/…
– ObjectiveTC
Oct 2 '17 at 6:47
An important note about unsubscribing from observables: ".takeUntil(this.ngUnsubscribe)". See this stack overflow question/answer, which appears to show the 'de-facto' recommended way to unsubscribe from events: stackoverflow.com/questions/38008334/…
– ObjectiveTC
Oct 2 '17 at 6:47
1
1
The alternative is to the .first() or .take(1) if the observable is only meant to receive data once. All other 'infinite streams' of observables should be unsubscribed in 'ngOnDestroy()', and if you don't then you may end up with duplicate 'observable' callbacks. stackoverflow.com/questions/28007777/…
– ObjectiveTC
Oct 2 '17 at 6:47
The alternative is to the .first() or .take(1) if the observable is only meant to receive data once. All other 'infinite streams' of observables should be unsubscribed in 'ngOnDestroy()', and if you don't then you may end up with duplicate 'observable' callbacks. stackoverflow.com/questions/28007777/…
– ObjectiveTC
Oct 2 '17 at 6:47
add a comment |
I assume that @ngx-cache/core could be useful to maintain caching features for the http calls, especially if the HTTP call is made both on browser and server platforms.
Let's say we have the following method:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
You can use the Cached
decorator of @ngx-cache/core to store the returned value from the method making the HTTP call at the cache storage
(the storage
can be configurable, please check the implementation at ng-seed/universal) - right on the first execution. The next times the method is invoked (no matter on browser or server platform), the value is retrieved from the cache storage
.
import { Cached } from '@ngx-cache/core';
...
@Cached('get-customer') // the cache key/identifier
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
There's also the possibility to use caching methods (has
, get
, set
) using the caching API.
anyclass.ts
...
import { CacheService } from '@ngx-cache/core';
@Injectable()
export class AnyClass {
constructor(private readonly cache: CacheService) {
// note that CacheService is injected into a private property of AnyClass
}
// will retrieve 'some string value'
getSomeStringValue(): string {
if (this.cache.has('some-string'))
return this.cache.get('some-string');
this.cache.set('some-string', 'some string value');
return 'some string value';
}
}
Here are the list of packages, both for client-side and server-side caching:
@ngx-cache/core: cache utility
@ngx-cache/platform-browser: SPA/Browser platform implementation
@ngx-cache/platform-server: server platform implementation
@ngx-cache/fs-storage: storage utility (required for server platform)
add a comment |
I assume that @ngx-cache/core could be useful to maintain caching features for the http calls, especially if the HTTP call is made both on browser and server platforms.
Let's say we have the following method:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
You can use the Cached
decorator of @ngx-cache/core to store the returned value from the method making the HTTP call at the cache storage
(the storage
can be configurable, please check the implementation at ng-seed/universal) - right on the first execution. The next times the method is invoked (no matter on browser or server platform), the value is retrieved from the cache storage
.
import { Cached } from '@ngx-cache/core';
...
@Cached('get-customer') // the cache key/identifier
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
There's also the possibility to use caching methods (has
, get
, set
) using the caching API.
anyclass.ts
...
import { CacheService } from '@ngx-cache/core';
@Injectable()
export class AnyClass {
constructor(private readonly cache: CacheService) {
// note that CacheService is injected into a private property of AnyClass
}
// will retrieve 'some string value'
getSomeStringValue(): string {
if (this.cache.has('some-string'))
return this.cache.get('some-string');
this.cache.set('some-string', 'some string value');
return 'some string value';
}
}
Here are the list of packages, both for client-side and server-side caching:
@ngx-cache/core: cache utility
@ngx-cache/platform-browser: SPA/Browser platform implementation
@ngx-cache/platform-server: server platform implementation
@ngx-cache/fs-storage: storage utility (required for server platform)
add a comment |
I assume that @ngx-cache/core could be useful to maintain caching features for the http calls, especially if the HTTP call is made both on browser and server platforms.
Let's say we have the following method:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
You can use the Cached
decorator of @ngx-cache/core to store the returned value from the method making the HTTP call at the cache storage
(the storage
can be configurable, please check the implementation at ng-seed/universal) - right on the first execution. The next times the method is invoked (no matter on browser or server platform), the value is retrieved from the cache storage
.
import { Cached } from '@ngx-cache/core';
...
@Cached('get-customer') // the cache key/identifier
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
There's also the possibility to use caching methods (has
, get
, set
) using the caching API.
anyclass.ts
...
import { CacheService } from '@ngx-cache/core';
@Injectable()
export class AnyClass {
constructor(private readonly cache: CacheService) {
// note that CacheService is injected into a private property of AnyClass
}
// will retrieve 'some string value'
getSomeStringValue(): string {
if (this.cache.has('some-string'))
return this.cache.get('some-string');
this.cache.set('some-string', 'some string value');
return 'some string value';
}
}
Here are the list of packages, both for client-side and server-side caching:
@ngx-cache/core: cache utility
@ngx-cache/platform-browser: SPA/Browser platform implementation
@ngx-cache/platform-server: server platform implementation
@ngx-cache/fs-storage: storage utility (required for server platform)
I assume that @ngx-cache/core could be useful to maintain caching features for the http calls, especially if the HTTP call is made both on browser and server platforms.
Let's say we have the following method:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
You can use the Cached
decorator of @ngx-cache/core to store the returned value from the method making the HTTP call at the cache storage
(the storage
can be configurable, please check the implementation at ng-seed/universal) - right on the first execution. The next times the method is invoked (no matter on browser or server platform), the value is retrieved from the cache storage
.
import { Cached } from '@ngx-cache/core';
...
@Cached('get-customer') // the cache key/identifier
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
There's also the possibility to use caching methods (has
, get
, set
) using the caching API.
anyclass.ts
...
import { CacheService } from '@ngx-cache/core';
@Injectable()
export class AnyClass {
constructor(private readonly cache: CacheService) {
// note that CacheService is injected into a private property of AnyClass
}
// will retrieve 'some string value'
getSomeStringValue(): string {
if (this.cache.has('some-string'))
return this.cache.get('some-string');
this.cache.set('some-string', 'some string value');
return 'some string value';
}
}
Here are the list of packages, both for client-side and server-side caching:
@ngx-cache/core: cache utility
@ngx-cache/platform-browser: SPA/Browser platform implementation
@ngx-cache/platform-server: server platform implementation
@ngx-cache/fs-storage: storage utility (required for server platform)
answered May 3 '17 at 7:03


Burak Tasci
627816
627816
add a comment |
add a comment |
rxjs 5.3.0
I haven't been happy with .map(myFunction).publishReplay(1).refCount()
With multiple subscribers, .map()
executes myFunction
twice in some cases (I expect it to only execute once). One fix seems to be publishReplay(1).refCount().take(1)
Another thing you can do, is just not use refCount()
and make the Observable hot right away:
let obs = this.http.get('my/data.json').publishReplay(1);
obs.connect();
return obs;
This will start the HTTP request regardless of subscribers. I'm not sure if unsubscribing before the HTTP GET finishes will cancel it or not.
add a comment |
rxjs 5.3.0
I haven't been happy with .map(myFunction).publishReplay(1).refCount()
With multiple subscribers, .map()
executes myFunction
twice in some cases (I expect it to only execute once). One fix seems to be publishReplay(1).refCount().take(1)
Another thing you can do, is just not use refCount()
and make the Observable hot right away:
let obs = this.http.get('my/data.json').publishReplay(1);
obs.connect();
return obs;
This will start the HTTP request regardless of subscribers. I'm not sure if unsubscribing before the HTTP GET finishes will cancel it or not.
add a comment |
rxjs 5.3.0
I haven't been happy with .map(myFunction).publishReplay(1).refCount()
With multiple subscribers, .map()
executes myFunction
twice in some cases (I expect it to only execute once). One fix seems to be publishReplay(1).refCount().take(1)
Another thing you can do, is just not use refCount()
and make the Observable hot right away:
let obs = this.http.get('my/data.json').publishReplay(1);
obs.connect();
return obs;
This will start the HTTP request regardless of subscribers. I'm not sure if unsubscribing before the HTTP GET finishes will cancel it or not.
rxjs 5.3.0
I haven't been happy with .map(myFunction).publishReplay(1).refCount()
With multiple subscribers, .map()
executes myFunction
twice in some cases (I expect it to only execute once). One fix seems to be publishReplay(1).refCount().take(1)
Another thing you can do, is just not use refCount()
and make the Observable hot right away:
let obs = this.http.get('my/data.json').publishReplay(1);
obs.connect();
return obs;
This will start the HTTP request regardless of subscribers. I'm not sure if unsubscribing before the HTTP GET finishes will cancel it or not.
answered Apr 22 '17 at 0:26


Arlo
7381818
7381818
add a comment |
add a comment |
What we want to do, is ensure that this does not cause multiple network requests.
My personal favourite is to make use of async
methods for calls that make network requests. The methods themselves don't return a value, instead they update a BehaviorSubject
within the same service, which components will subscribe to.
Now Why use a BehaviorSubject
instead of an Observable
? Because,
- Upon subscription BehaviorSubject returns the last value whereas A regular observable only triggers when it receives an
onnext
. - If you want to retrieve the last value of the BehaviorSubject in a non-observable code (without a subscription), you can use the
getValue()
method.
Example:
customer.service.ts
public customers$: BehaviorSubject<Customer> = new BehaviorSubject();
public async getCustomers(): Promise<void> {
let customers = await this.httpClient.post<LogEntry>(this.endPoint, criteria).toPromise();
if (customers)
this.customers$.next(customers);
}
Then, wherever required, we can just subscribe to customers$
.
public ngOnInit(): void {
this.customerService.customers$
.subscribe((customers: Customer) => this.customerList = customers);
}
Or maybe you want to use it directly in a template
<li *ngFor="let customer of customerService.customers$ | async"> ... </li>
So now, until you make another call to getCustomers
, the data is retained in the customers$
BehaviorSubject.
So what if you want to refresh this data? just make a call to getCustomers()
public async refresh(): Promise<void> {
try {
await this.customerService.getCustomers();
}
catch (e) {
// request failed, handle exception
console.error(e);
}
}
Using this method, we don't have to explicitly retain the data between subsequent network calls as it's handled by the BehaviorSubject
.
PS: Usually when a component gets destroyed it's a good practice to get rid of the subscriptions, for that you can use the method suggested in this answer.
add a comment |
What we want to do, is ensure that this does not cause multiple network requests.
My personal favourite is to make use of async
methods for calls that make network requests. The methods themselves don't return a value, instead they update a BehaviorSubject
within the same service, which components will subscribe to.
Now Why use a BehaviorSubject
instead of an Observable
? Because,
- Upon subscription BehaviorSubject returns the last value whereas A regular observable only triggers when it receives an
onnext
. - If you want to retrieve the last value of the BehaviorSubject in a non-observable code (without a subscription), you can use the
getValue()
method.
Example:
customer.service.ts
public customers$: BehaviorSubject<Customer> = new BehaviorSubject();
public async getCustomers(): Promise<void> {
let customers = await this.httpClient.post<LogEntry>(this.endPoint, criteria).toPromise();
if (customers)
this.customers$.next(customers);
}
Then, wherever required, we can just subscribe to customers$
.
public ngOnInit(): void {
this.customerService.customers$
.subscribe((customers: Customer) => this.customerList = customers);
}
Or maybe you want to use it directly in a template
<li *ngFor="let customer of customerService.customers$ | async"> ... </li>
So now, until you make another call to getCustomers
, the data is retained in the customers$
BehaviorSubject.
So what if you want to refresh this data? just make a call to getCustomers()
public async refresh(): Promise<void> {
try {
await this.customerService.getCustomers();
}
catch (e) {
// request failed, handle exception
console.error(e);
}
}
Using this method, we don't have to explicitly retain the data between subsequent network calls as it's handled by the BehaviorSubject
.
PS: Usually when a component gets destroyed it's a good practice to get rid of the subscriptions, for that you can use the method suggested in this answer.
add a comment |
What we want to do, is ensure that this does not cause multiple network requests.
My personal favourite is to make use of async
methods for calls that make network requests. The methods themselves don't return a value, instead they update a BehaviorSubject
within the same service, which components will subscribe to.
Now Why use a BehaviorSubject
instead of an Observable
? Because,
- Upon subscription BehaviorSubject returns the last value whereas A regular observable only triggers when it receives an
onnext
. - If you want to retrieve the last value of the BehaviorSubject in a non-observable code (without a subscription), you can use the
getValue()
method.
Example:
customer.service.ts
public customers$: BehaviorSubject<Customer> = new BehaviorSubject();
public async getCustomers(): Promise<void> {
let customers = await this.httpClient.post<LogEntry>(this.endPoint, criteria).toPromise();
if (customers)
this.customers$.next(customers);
}
Then, wherever required, we can just subscribe to customers$
.
public ngOnInit(): void {
this.customerService.customers$
.subscribe((customers: Customer) => this.customerList = customers);
}
Or maybe you want to use it directly in a template
<li *ngFor="let customer of customerService.customers$ | async"> ... </li>
So now, until you make another call to getCustomers
, the data is retained in the customers$
BehaviorSubject.
So what if you want to refresh this data? just make a call to getCustomers()
public async refresh(): Promise<void> {
try {
await this.customerService.getCustomers();
}
catch (e) {
// request failed, handle exception
console.error(e);
}
}
Using this method, we don't have to explicitly retain the data between subsequent network calls as it's handled by the BehaviorSubject
.
PS: Usually when a component gets destroyed it's a good practice to get rid of the subscriptions, for that you can use the method suggested in this answer.
What we want to do, is ensure that this does not cause multiple network requests.
My personal favourite is to make use of async
methods for calls that make network requests. The methods themselves don't return a value, instead they update a BehaviorSubject
within the same service, which components will subscribe to.
Now Why use a BehaviorSubject
instead of an Observable
? Because,
- Upon subscription BehaviorSubject returns the last value whereas A regular observable only triggers when it receives an
onnext
. - If you want to retrieve the last value of the BehaviorSubject in a non-observable code (without a subscription), you can use the
getValue()
method.
Example:
customer.service.ts
public customers$: BehaviorSubject<Customer> = new BehaviorSubject();
public async getCustomers(): Promise<void> {
let customers = await this.httpClient.post<LogEntry>(this.endPoint, criteria).toPromise();
if (customers)
this.customers$.next(customers);
}
Then, wherever required, we can just subscribe to customers$
.
public ngOnInit(): void {
this.customerService.customers$
.subscribe((customers: Customer) => this.customerList = customers);
}
Or maybe you want to use it directly in a template
<li *ngFor="let customer of customerService.customers$ | async"> ... </li>
So now, until you make another call to getCustomers
, the data is retained in the customers$
BehaviorSubject.
So what if you want to refresh this data? just make a call to getCustomers()
public async refresh(): Promise<void> {
try {
await this.customerService.getCustomers();
}
catch (e) {
// request failed, handle exception
console.error(e);
}
}
Using this method, we don't have to explicitly retain the data between subsequent network calls as it's handled by the BehaviorSubject
.
PS: Usually when a component gets destroyed it's a good practice to get rid of the subscriptions, for that you can use the method suggested in this answer.
answered Mar 24 '18 at 1:36


cyberpirate92
1,36221529
1,36221529
add a comment |
add a comment |
Just call share() after map and before any subscribe.
In my case, I have a generic service (RestClientService.ts) who is making the rest call, extracting data, check for errors and returning observable to a concrete implementation service (f.ex.: ContractClientService.ts), finally this concrete implementation returns observable to de ContractComponent.ts, and this one subscribe to update the view.
RestClientService.ts:
export abstract class RestClientService<T extends BaseModel> {
public GetAll = (path: string, property: string): Observable<T> => {
let fullPath = this.actionUrl + path;
let observable = this._http.get(fullPath).map(res => this.extractData(res, property));
observable = observable.share(); //allows multiple subscribers without making again the http request
observable.subscribe(
(res) => {},
error => this.handleError2(error, "GetAll", fullPath),
() => {}
);
return observable;
}
private extractData(res: Response, property: string) {
...
}
private handleError2(error: any, method: string, path: string) {
...
}
}
ContractService.ts:
export class ContractService extends RestClientService<Contract> {
private GET_ALL_ITEMS_REST_URI_PATH = "search";
private GET_ALL_ITEMS_PROPERTY_PATH = "contract";
public getAllItems(): Observable<Contract> {
return this.GetAll(this.GET_ALL_ITEMS_REST_URI_PATH, this.GET_ALL_ITEMS_PROPERTY_PATH);
}
}
ContractComponent.ts:
export class ContractComponent implements OnInit {
getAllItems() {
this.rcService.getAllItems().subscribe((data) => {
this.items = data;
});
}
}
add a comment |
Just call share() after map and before any subscribe.
In my case, I have a generic service (RestClientService.ts) who is making the rest call, extracting data, check for errors and returning observable to a concrete implementation service (f.ex.: ContractClientService.ts), finally this concrete implementation returns observable to de ContractComponent.ts, and this one subscribe to update the view.
RestClientService.ts:
export abstract class RestClientService<T extends BaseModel> {
public GetAll = (path: string, property: string): Observable<T> => {
let fullPath = this.actionUrl + path;
let observable = this._http.get(fullPath).map(res => this.extractData(res, property));
observable = observable.share(); //allows multiple subscribers without making again the http request
observable.subscribe(
(res) => {},
error => this.handleError2(error, "GetAll", fullPath),
() => {}
);
return observable;
}
private extractData(res: Response, property: string) {
...
}
private handleError2(error: any, method: string, path: string) {
...
}
}
ContractService.ts:
export class ContractService extends RestClientService<Contract> {
private GET_ALL_ITEMS_REST_URI_PATH = "search";
private GET_ALL_ITEMS_PROPERTY_PATH = "contract";
public getAllItems(): Observable<Contract> {
return this.GetAll(this.GET_ALL_ITEMS_REST_URI_PATH, this.GET_ALL_ITEMS_PROPERTY_PATH);
}
}
ContractComponent.ts:
export class ContractComponent implements OnInit {
getAllItems() {
this.rcService.getAllItems().subscribe((data) => {
this.items = data;
});
}
}
add a comment |
Just call share() after map and before any subscribe.
In my case, I have a generic service (RestClientService.ts) who is making the rest call, extracting data, check for errors and returning observable to a concrete implementation service (f.ex.: ContractClientService.ts), finally this concrete implementation returns observable to de ContractComponent.ts, and this one subscribe to update the view.
RestClientService.ts:
export abstract class RestClientService<T extends BaseModel> {
public GetAll = (path: string, property: string): Observable<T> => {
let fullPath = this.actionUrl + path;
let observable = this._http.get(fullPath).map(res => this.extractData(res, property));
observable = observable.share(); //allows multiple subscribers without making again the http request
observable.subscribe(
(res) => {},
error => this.handleError2(error, "GetAll", fullPath),
() => {}
);
return observable;
}
private extractData(res: Response, property: string) {
...
}
private handleError2(error: any, method: string, path: string) {
...
}
}
ContractService.ts:
export class ContractService extends RestClientService<Contract> {
private GET_ALL_ITEMS_REST_URI_PATH = "search";
private GET_ALL_ITEMS_PROPERTY_PATH = "contract";
public getAllItems(): Observable<Contract> {
return this.GetAll(this.GET_ALL_ITEMS_REST_URI_PATH, this.GET_ALL_ITEMS_PROPERTY_PATH);
}
}
ContractComponent.ts:
export class ContractComponent implements OnInit {
getAllItems() {
this.rcService.getAllItems().subscribe((data) => {
this.items = data;
});
}
}
Just call share() after map and before any subscribe.
In my case, I have a generic service (RestClientService.ts) who is making the rest call, extracting data, check for errors and returning observable to a concrete implementation service (f.ex.: ContractClientService.ts), finally this concrete implementation returns observable to de ContractComponent.ts, and this one subscribe to update the view.
RestClientService.ts:
export abstract class RestClientService<T extends BaseModel> {
public GetAll = (path: string, property: string): Observable<T> => {
let fullPath = this.actionUrl + path;
let observable = this._http.get(fullPath).map(res => this.extractData(res, property));
observable = observable.share(); //allows multiple subscribers without making again the http request
observable.subscribe(
(res) => {},
error => this.handleError2(error, "GetAll", fullPath),
() => {}
);
return observable;
}
private extractData(res: Response, property: string) {
...
}
private handleError2(error: any, method: string, path: string) {
...
}
}
ContractService.ts:
export class ContractService extends RestClientService<Contract> {
private GET_ALL_ITEMS_REST_URI_PATH = "search";
private GET_ALL_ITEMS_PROPERTY_PATH = "contract";
public getAllItems(): Observable<Contract> {
return this.GetAll(this.GET_ALL_ITEMS_REST_URI_PATH, this.GET_ALL_ITEMS_PROPERTY_PATH);
}
}
ContractComponent.ts:
export class ContractComponent implements OnInit {
getAllItems() {
this.rcService.getAllItems().subscribe((data) => {
this.items = data;
});
}
}
answered May 23 '16 at 9:43


surfealokesea
2,47111829
2,47111829
add a comment |
add a comment |
I wrote a cache class,
/**
* Caches results returned from given fetcher callback for given key,
* up to maxItems results, deletes the oldest results when full (FIFO).
*/
export class StaticCache
{
static cachedData: Map<string, any> = new Map<string, any>();
static maxItems: number = 400;
static get(key: string){
return this.cachedData.get(key);
}
static getOrFetch(key: string, fetcher: (string) => any): any {
let value = this.cachedData.get(key);
if (value != null){
console.log("Cache HIT! (fetcher)");
return value;
}
console.log("Cache MISS... (fetcher)");
value = fetcher(key);
this.add(key, value);
return value;
}
static add(key, value){
this.cachedData.set(key, value);
this.deleteOverflowing();
}
static deleteOverflowing(): void {
if (this.cachedData.size > this.maxItems) {
this.deleteOldest(this.cachedData.size - this.maxItems);
}
}
/// A Map object iterates its elements in insertion order — a for...of loop returns an array of [key, value] for each iteration.
/// However that seems not to work. Trying with forEach.
static deleteOldest(howMany: number): void {
//console.debug("Deleting oldest " + howMany + " of " + this.cachedData.size);
let iterKeys = this.cachedData.keys();
let item: IteratorResult<string>;
while (howMany-- > 0 && (item = iterKeys.next(), !item.done)){
//console.debug(" Deleting: " + item.value);
this.cachedData.delete(item.value); // Deleting while iterating should be ok in JS.
}
}
static clear(): void {
this.cachedData = new Map<string, any>();
}
}
It's all static because of how we use it, but feel free to make it a normal class and a service. I'm not sure if angular keeps a single instance for the whole time though (new to Angular2).
And this is how I use it:
let httpService: Http = this.http;
function fetcher(url: string): Observable<any> {
console.log(" Fetching URL: " + url);
return httpService.get(url).map((response: Response) => {
if (!response) return null;
if (typeof response.json() !== "array")
throw new Error("Graph REST should return an array of vertices.");
let items: any = graphService.fromJSONarray(response.json(), httpService);
return array ? items : items[0];
});
}
// If data is a link, return a result of a service call.
if (this.data[verticesLabel][name]["link"] || this.data[verticesLabel][name]["_type"] == "link")
{
// Make an HTTP call.
let url = this.data[verticesLabel][name]["link"];
let cachedObservable: Observable<any> = StaticCache.getOrFetch(url, fetcher);
if (!cachedObservable)
throw new Error("Failed loading link: " + url);
return cachedObservable;
}
I assume there could be a more clever way, which would use some Observable
tricks but this was just fine for my purposes.
add a comment |
I wrote a cache class,
/**
* Caches results returned from given fetcher callback for given key,
* up to maxItems results, deletes the oldest results when full (FIFO).
*/
export class StaticCache
{
static cachedData: Map<string, any> = new Map<string, any>();
static maxItems: number = 400;
static get(key: string){
return this.cachedData.get(key);
}
static getOrFetch(key: string, fetcher: (string) => any): any {
let value = this.cachedData.get(key);
if (value != null){
console.log("Cache HIT! (fetcher)");
return value;
}
console.log("Cache MISS... (fetcher)");
value = fetcher(key);
this.add(key, value);
return value;
}
static add(key, value){
this.cachedData.set(key, value);
this.deleteOverflowing();
}
static deleteOverflowing(): void {
if (this.cachedData.size > this.maxItems) {
this.deleteOldest(this.cachedData.size - this.maxItems);
}
}
/// A Map object iterates its elements in insertion order — a for...of loop returns an array of [key, value] for each iteration.
/// However that seems not to work. Trying with forEach.
static deleteOldest(howMany: number): void {
//console.debug("Deleting oldest " + howMany + " of " + this.cachedData.size);
let iterKeys = this.cachedData.keys();
let item: IteratorResult<string>;
while (howMany-- > 0 && (item = iterKeys.next(), !item.done)){
//console.debug(" Deleting: " + item.value);
this.cachedData.delete(item.value); // Deleting while iterating should be ok in JS.
}
}
static clear(): void {
this.cachedData = new Map<string, any>();
}
}
It's all static because of how we use it, but feel free to make it a normal class and a service. I'm not sure if angular keeps a single instance for the whole time though (new to Angular2).
And this is how I use it:
let httpService: Http = this.http;
function fetcher(url: string): Observable<any> {
console.log(" Fetching URL: " + url);
return httpService.get(url).map((response: Response) => {
if (!response) return null;
if (typeof response.json() !== "array")
throw new Error("Graph REST should return an array of vertices.");
let items: any = graphService.fromJSONarray(response.json(), httpService);
return array ? items : items[0];
});
}
// If data is a link, return a result of a service call.
if (this.data[verticesLabel][name]["link"] || this.data[verticesLabel][name]["_type"] == "link")
{
// Make an HTTP call.
let url = this.data[verticesLabel][name]["link"];
let cachedObservable: Observable<any> = StaticCache.getOrFetch(url, fetcher);
if (!cachedObservable)
throw new Error("Failed loading link: " + url);
return cachedObservable;
}
I assume there could be a more clever way, which would use some Observable
tricks but this was just fine for my purposes.
add a comment |
I wrote a cache class,
/**
* Caches results returned from given fetcher callback for given key,
* up to maxItems results, deletes the oldest results when full (FIFO).
*/
export class StaticCache
{
static cachedData: Map<string, any> = new Map<string, any>();
static maxItems: number = 400;
static get(key: string){
return this.cachedData.get(key);
}
static getOrFetch(key: string, fetcher: (string) => any): any {
let value = this.cachedData.get(key);
if (value != null){
console.log("Cache HIT! (fetcher)");
return value;
}
console.log("Cache MISS... (fetcher)");
value = fetcher(key);
this.add(key, value);
return value;
}
static add(key, value){
this.cachedData.set(key, value);
this.deleteOverflowing();
}
static deleteOverflowing(): void {
if (this.cachedData.size > this.maxItems) {
this.deleteOldest(this.cachedData.size - this.maxItems);
}
}
/// A Map object iterates its elements in insertion order — a for...of loop returns an array of [key, value] for each iteration.
/// However that seems not to work. Trying with forEach.
static deleteOldest(howMany: number): void {
//console.debug("Deleting oldest " + howMany + " of " + this.cachedData.size);
let iterKeys = this.cachedData.keys();
let item: IteratorResult<string>;
while (howMany-- > 0 && (item = iterKeys.next(), !item.done)){
//console.debug(" Deleting: " + item.value);
this.cachedData.delete(item.value); // Deleting while iterating should be ok in JS.
}
}
static clear(): void {
this.cachedData = new Map<string, any>();
}
}
It's all static because of how we use it, but feel free to make it a normal class and a service. I'm not sure if angular keeps a single instance for the whole time though (new to Angular2).
And this is how I use it:
let httpService: Http = this.http;
function fetcher(url: string): Observable<any> {
console.log(" Fetching URL: " + url);
return httpService.get(url).map((response: Response) => {
if (!response) return null;
if (typeof response.json() !== "array")
throw new Error("Graph REST should return an array of vertices.");
let items: any = graphService.fromJSONarray(response.json(), httpService);
return array ? items : items[0];
});
}
// If data is a link, return a result of a service call.
if (this.data[verticesLabel][name]["link"] || this.data[verticesLabel][name]["_type"] == "link")
{
// Make an HTTP call.
let url = this.data[verticesLabel][name]["link"];
let cachedObservable: Observable<any> = StaticCache.getOrFetch(url, fetcher);
if (!cachedObservable)
throw new Error("Failed loading link: " + url);
return cachedObservable;
}
I assume there could be a more clever way, which would use some Observable
tricks but this was just fine for my purposes.
I wrote a cache class,
/**
* Caches results returned from given fetcher callback for given key,
* up to maxItems results, deletes the oldest results when full (FIFO).
*/
export class StaticCache
{
static cachedData: Map<string, any> = new Map<string, any>();
static maxItems: number = 400;
static get(key: string){
return this.cachedData.get(key);
}
static getOrFetch(key: string, fetcher: (string) => any): any {
let value = this.cachedData.get(key);
if (value != null){
console.log("Cache HIT! (fetcher)");
return value;
}
console.log("Cache MISS... (fetcher)");
value = fetcher(key);
this.add(key, value);
return value;
}
static add(key, value){
this.cachedData.set(key, value);
this.deleteOverflowing();
}
static deleteOverflowing(): void {
if (this.cachedData.size > this.maxItems) {
this.deleteOldest(this.cachedData.size - this.maxItems);
}
}
/// A Map object iterates its elements in insertion order — a for...of loop returns an array of [key, value] for each iteration.
/// However that seems not to work. Trying with forEach.
static deleteOldest(howMany: number): void {
//console.debug("Deleting oldest " + howMany + " of " + this.cachedData.size);
let iterKeys = this.cachedData.keys();
let item: IteratorResult<string>;
while (howMany-- > 0 && (item = iterKeys.next(), !item.done)){
//console.debug(" Deleting: " + item.value);
this.cachedData.delete(item.value); // Deleting while iterating should be ok in JS.
}
}
static clear(): void {
this.cachedData = new Map<string, any>();
}
}
It's all static because of how we use it, but feel free to make it a normal class and a service. I'm not sure if angular keeps a single instance for the whole time though (new to Angular2).
And this is how I use it:
let httpService: Http = this.http;
function fetcher(url: string): Observable<any> {
console.log(" Fetching URL: " + url);
return httpService.get(url).map((response: Response) => {
if (!response) return null;
if (typeof response.json() !== "array")
throw new Error("Graph REST should return an array of vertices.");
let items: any = graphService.fromJSONarray(response.json(), httpService);
return array ? items : items[0];
});
}
// If data is a link, return a result of a service call.
if (this.data[verticesLabel][name]["link"] || this.data[verticesLabel][name]["_type"] == "link")
{
// Make an HTTP call.
let url = this.data[verticesLabel][name]["link"];
let cachedObservable: Observable<any> = StaticCache.getOrFetch(url, fetcher);
if (!cachedObservable)
throw new Error("Failed loading link: " + url);
return cachedObservable;
}
I assume there could be a more clever way, which would use some Observable
tricks but this was just fine for my purposes.
answered Dec 16 '16 at 14:40
Ondra Žižka
19.8k23135209
19.8k23135209
add a comment |
add a comment |
Just use this cache layer, it does everything you requires, and even manage cache for ajax requests.
http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html
It's this much easy to use
@Component({
selector: 'home',
templateUrl: './html/home.component.html',
styleUrls: ['./css/home.component.css'],
})
export class HomeComponent {
constructor(AjaxService:AjaxService){
AjaxService.postCache("/api/home/articles").subscribe(values=>{console.log(values);this.articles=values;});
}
articles={1:[{data:[{title:"first",sort_text:"description"},{title:"second",sort_text:"description"}],type:"Open Source Works"}]};
}
The layer(as an inject-able angular service) is
import { Injectable } from '@angular/core';
import { Http, Response} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import './../rxjs/operator'
@Injectable()
export class AjaxService {
public data:Object={};
/*
private dataObservable:Observable<boolean>;
*/
private dataObserver:Array<any>=;
private loading:Object={};
private links:Object={};
counter:number=-1;
constructor (private http: Http) {
}
private loadPostCache(link:string){
if(!this.loading[link]){
this.loading[link]=true;
this.links[link].forEach(a=>this.dataObserver[a].next(false));
this.http.get(link)
.map(this.setValue)
.catch(this.handleError).subscribe(
values => {
this.data[link] = values;
delete this.loading[link];
this.links[link].forEach(a=>this.dataObserver[a].next(false));
},
error => {
delete this.loading[link];
}
);
}
}
private setValue(res: Response) {
return res.json() || { };
}
private handleError (error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
postCache(link:string): Observable<Object>{
return Observable.create(observer=> {
if(this.data.hasOwnProperty(link)){
observer.next(this.data[link]);
}
else{
let _observable=Observable.create(_observer=>{
this.counter=this.counter+1;
this.dataObserver[this.counter]=_observer;
this.links.hasOwnProperty(link)?this.links[link].push(this.counter):(this.links[link]=[this.counter]);
_observer.next(false);
});
this.loadPostCache(link);
_observable.subscribe(status=>{
if(status){
observer.next(this.data[link]);
}
}
);
}
});
}
}
add a comment |
Just use this cache layer, it does everything you requires, and even manage cache for ajax requests.
http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html
It's this much easy to use
@Component({
selector: 'home',
templateUrl: './html/home.component.html',
styleUrls: ['./css/home.component.css'],
})
export class HomeComponent {
constructor(AjaxService:AjaxService){
AjaxService.postCache("/api/home/articles").subscribe(values=>{console.log(values);this.articles=values;});
}
articles={1:[{data:[{title:"first",sort_text:"description"},{title:"second",sort_text:"description"}],type:"Open Source Works"}]};
}
The layer(as an inject-able angular service) is
import { Injectable } from '@angular/core';
import { Http, Response} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import './../rxjs/operator'
@Injectable()
export class AjaxService {
public data:Object={};
/*
private dataObservable:Observable<boolean>;
*/
private dataObserver:Array<any>=;
private loading:Object={};
private links:Object={};
counter:number=-1;
constructor (private http: Http) {
}
private loadPostCache(link:string){
if(!this.loading[link]){
this.loading[link]=true;
this.links[link].forEach(a=>this.dataObserver[a].next(false));
this.http.get(link)
.map(this.setValue)
.catch(this.handleError).subscribe(
values => {
this.data[link] = values;
delete this.loading[link];
this.links[link].forEach(a=>this.dataObserver[a].next(false));
},
error => {
delete this.loading[link];
}
);
}
}
private setValue(res: Response) {
return res.json() || { };
}
private handleError (error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
postCache(link:string): Observable<Object>{
return Observable.create(observer=> {
if(this.data.hasOwnProperty(link)){
observer.next(this.data[link]);
}
else{
let _observable=Observable.create(_observer=>{
this.counter=this.counter+1;
this.dataObserver[this.counter]=_observer;
this.links.hasOwnProperty(link)?this.links[link].push(this.counter):(this.links[link]=[this.counter]);
_observer.next(false);
});
this.loadPostCache(link);
_observable.subscribe(status=>{
if(status){
observer.next(this.data[link]);
}
}
);
}
});
}
}
add a comment |
Just use this cache layer, it does everything you requires, and even manage cache for ajax requests.
http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html
It's this much easy to use
@Component({
selector: 'home',
templateUrl: './html/home.component.html',
styleUrls: ['./css/home.component.css'],
})
export class HomeComponent {
constructor(AjaxService:AjaxService){
AjaxService.postCache("/api/home/articles").subscribe(values=>{console.log(values);this.articles=values;});
}
articles={1:[{data:[{title:"first",sort_text:"description"},{title:"second",sort_text:"description"}],type:"Open Source Works"}]};
}
The layer(as an inject-able angular service) is
import { Injectable } from '@angular/core';
import { Http, Response} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import './../rxjs/operator'
@Injectable()
export class AjaxService {
public data:Object={};
/*
private dataObservable:Observable<boolean>;
*/
private dataObserver:Array<any>=;
private loading:Object={};
private links:Object={};
counter:number=-1;
constructor (private http: Http) {
}
private loadPostCache(link:string){
if(!this.loading[link]){
this.loading[link]=true;
this.links[link].forEach(a=>this.dataObserver[a].next(false));
this.http.get(link)
.map(this.setValue)
.catch(this.handleError).subscribe(
values => {
this.data[link] = values;
delete this.loading[link];
this.links[link].forEach(a=>this.dataObserver[a].next(false));
},
error => {
delete this.loading[link];
}
);
}
}
private setValue(res: Response) {
return res.json() || { };
}
private handleError (error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
postCache(link:string): Observable<Object>{
return Observable.create(observer=> {
if(this.data.hasOwnProperty(link)){
observer.next(this.data[link]);
}
else{
let _observable=Observable.create(_observer=>{
this.counter=this.counter+1;
this.dataObserver[this.counter]=_observer;
this.links.hasOwnProperty(link)?this.links[link].push(this.counter):(this.links[link]=[this.counter]);
_observer.next(false);
});
this.loadPostCache(link);
_observable.subscribe(status=>{
if(status){
observer.next(this.data[link]);
}
}
);
}
});
}
}
Just use this cache layer, it does everything you requires, and even manage cache for ajax requests.
http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html
It's this much easy to use
@Component({
selector: 'home',
templateUrl: './html/home.component.html',
styleUrls: ['./css/home.component.css'],
})
export class HomeComponent {
constructor(AjaxService:AjaxService){
AjaxService.postCache("/api/home/articles").subscribe(values=>{console.log(values);this.articles=values;});
}
articles={1:[{data:[{title:"first",sort_text:"description"},{title:"second",sort_text:"description"}],type:"Open Source Works"}]};
}
The layer(as an inject-able angular service) is
import { Injectable } from '@angular/core';
import { Http, Response} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import './../rxjs/operator'
@Injectable()
export class AjaxService {
public data:Object={};
/*
private dataObservable:Observable<boolean>;
*/
private dataObserver:Array<any>=;
private loading:Object={};
private links:Object={};
counter:number=-1;
constructor (private http: Http) {
}
private loadPostCache(link:string){
if(!this.loading[link]){
this.loading[link]=true;
this.links[link].forEach(a=>this.dataObserver[a].next(false));
this.http.get(link)
.map(this.setValue)
.catch(this.handleError).subscribe(
values => {
this.data[link] = values;
delete this.loading[link];
this.links[link].forEach(a=>this.dataObserver[a].next(false));
},
error => {
delete this.loading[link];
}
);
}
}
private setValue(res: Response) {
return res.json() || { };
}
private handleError (error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
postCache(link:string): Observable<Object>{
return Observable.create(observer=> {
if(this.data.hasOwnProperty(link)){
observer.next(this.data[link]);
}
else{
let _observable=Observable.create(_observer=>{
this.counter=this.counter+1;
this.dataObserver[this.counter]=_observer;
this.links.hasOwnProperty(link)?this.links[link].push(this.counter):(this.links[link]=[this.counter]);
_observer.next(false);
});
this.loadPostCache(link);
_observable.subscribe(status=>{
if(status){
observer.next(this.data[link]);
}
}
);
}
});
}
}
answered Jan 23 '17 at 10:52


Ravinder Payal
1,3591228
1,3591228
add a comment |
add a comment |
It's .publishReplay(1).refCount();
or .publishLast().refCount();
since Angular Http observables complete after request.
This simple class caches the result so you can subscribe to .value many times and makes only 1 request. You can also use .reload() to make new request and publish data.
You can use it like:
let res = new RestResource(() => this.http.get('inline.bundleo.js'));
res.status.subscribe((loading)=>{
console.log('STATUS=',loading);
});
res.value.subscribe((value) => {
console.log('VALUE=', value);
});
and the source:
export class RestResource {
static readonly LOADING: string = 'RestResource_Loading';
static readonly ERROR: string = 'RestResource_Error';
static readonly IDLE: string = 'RestResource_Idle';
public value: Observable<any>;
public status: Observable<string>;
private loadStatus: Observer<any>;
private reloader: Observable<any>;
private reloadTrigger: Observer<any>;
constructor(requestObservableFn: () => Observable<any>) {
this.status = Observable.create((o) => {
this.loadStatus = o;
});
this.reloader = Observable.create((o: Observer<any>) => {
this.reloadTrigger = o;
});
this.value = this.reloader.startWith(null).switchMap(() => {
if (this.loadStatus) {
this.loadStatus.next(RestResource.LOADING);
}
return requestObservableFn()
.map((res) => {
if (this.loadStatus) {
this.loadStatus.next(RestResource.IDLE);
}
return res;
}).catch((err)=>{
if (this.loadStatus) {
this.loadStatus.next(RestResource.ERROR);
}
return Observable.of(null);
});
}).publishReplay(1).refCount();
}
reload() {
this.reloadTrigger.next(null);
}
}
add a comment |
It's .publishReplay(1).refCount();
or .publishLast().refCount();
since Angular Http observables complete after request.
This simple class caches the result so you can subscribe to .value many times and makes only 1 request. You can also use .reload() to make new request and publish data.
You can use it like:
let res = new RestResource(() => this.http.get('inline.bundleo.js'));
res.status.subscribe((loading)=>{
console.log('STATUS=',loading);
});
res.value.subscribe((value) => {
console.log('VALUE=', value);
});
and the source:
export class RestResource {
static readonly LOADING: string = 'RestResource_Loading';
static readonly ERROR: string = 'RestResource_Error';
static readonly IDLE: string = 'RestResource_Idle';
public value: Observable<any>;
public status: Observable<string>;
private loadStatus: Observer<any>;
private reloader: Observable<any>;
private reloadTrigger: Observer<any>;
constructor(requestObservableFn: () => Observable<any>) {
this.status = Observable.create((o) => {
this.loadStatus = o;
});
this.reloader = Observable.create((o: Observer<any>) => {
this.reloadTrigger = o;
});
this.value = this.reloader.startWith(null).switchMap(() => {
if (this.loadStatus) {
this.loadStatus.next(RestResource.LOADING);
}
return requestObservableFn()
.map((res) => {
if (this.loadStatus) {
this.loadStatus.next(RestResource.IDLE);
}
return res;
}).catch((err)=>{
if (this.loadStatus) {
this.loadStatus.next(RestResource.ERROR);
}
return Observable.of(null);
});
}).publishReplay(1).refCount();
}
reload() {
this.reloadTrigger.next(null);
}
}
add a comment |
It's .publishReplay(1).refCount();
or .publishLast().refCount();
since Angular Http observables complete after request.
This simple class caches the result so you can subscribe to .value many times and makes only 1 request. You can also use .reload() to make new request and publish data.
You can use it like:
let res = new RestResource(() => this.http.get('inline.bundleo.js'));
res.status.subscribe((loading)=>{
console.log('STATUS=',loading);
});
res.value.subscribe((value) => {
console.log('VALUE=', value);
});
and the source:
export class RestResource {
static readonly LOADING: string = 'RestResource_Loading';
static readonly ERROR: string = 'RestResource_Error';
static readonly IDLE: string = 'RestResource_Idle';
public value: Observable<any>;
public status: Observable<string>;
private loadStatus: Observer<any>;
private reloader: Observable<any>;
private reloadTrigger: Observer<any>;
constructor(requestObservableFn: () => Observable<any>) {
this.status = Observable.create((o) => {
this.loadStatus = o;
});
this.reloader = Observable.create((o: Observer<any>) => {
this.reloadTrigger = o;
});
this.value = this.reloader.startWith(null).switchMap(() => {
if (this.loadStatus) {
this.loadStatus.next(RestResource.LOADING);
}
return requestObservableFn()
.map((res) => {
if (this.loadStatus) {
this.loadStatus.next(RestResource.IDLE);
}
return res;
}).catch((err)=>{
if (this.loadStatus) {
this.loadStatus.next(RestResource.ERROR);
}
return Observable.of(null);
});
}).publishReplay(1).refCount();
}
reload() {
this.reloadTrigger.next(null);
}
}
It's .publishReplay(1).refCount();
or .publishLast().refCount();
since Angular Http observables complete after request.
This simple class caches the result so you can subscribe to .value many times and makes only 1 request. You can also use .reload() to make new request and publish data.
You can use it like:
let res = new RestResource(() => this.http.get('inline.bundleo.js'));
res.status.subscribe((loading)=>{
console.log('STATUS=',loading);
});
res.value.subscribe((value) => {
console.log('VALUE=', value);
});
and the source:
export class RestResource {
static readonly LOADING: string = 'RestResource_Loading';
static readonly ERROR: string = 'RestResource_Error';
static readonly IDLE: string = 'RestResource_Idle';
public value: Observable<any>;
public status: Observable<string>;
private loadStatus: Observer<any>;
private reloader: Observable<any>;
private reloadTrigger: Observer<any>;
constructor(requestObservableFn: () => Observable<any>) {
this.status = Observable.create((o) => {
this.loadStatus = o;
});
this.reloader = Observable.create((o: Observer<any>) => {
this.reloadTrigger = o;
});
this.value = this.reloader.startWith(null).switchMap(() => {
if (this.loadStatus) {
this.loadStatus.next(RestResource.LOADING);
}
return requestObservableFn()
.map((res) => {
if (this.loadStatus) {
this.loadStatus.next(RestResource.IDLE);
}
return res;
}).catch((err)=>{
if (this.loadStatus) {
this.loadStatus.next(RestResource.ERROR);
}
return Observable.of(null);
});
}).publishReplay(1).refCount();
}
reload() {
this.reloadTrigger.next(null);
}
}
answered Nov 14 '17 at 22:40
Matjaz Hirsman
913
913
add a comment |
add a comment |
You can build simple class Cacheable<> that helps managing data retrieved from http server with multiple subscribers:
declare type GetDataHandler<T> = () => Observable<T>;
export class Cacheable<T> {
protected data: T;
protected subjectData: Subject<T>;
protected observableData: Observable<T>;
public getHandler: GetDataHandler<T>;
constructor() {
this.subjectData = new ReplaySubject(1);
this.observableData = this.subjectData.asObservable();
}
public getData(): Observable<T> {
if (!this.getHandler) {
throw new Error("getHandler is not defined");
}
if (!this.data) {
this.getHandler().map((r: T) => {
this.data = r;
return r;
}).subscribe(
result => this.subjectData.next(result),
err => this.subjectData.error(err)
);
}
return this.observableData;
}
public resetCache(): void {
this.data = null;
}
public refresh(): void {
this.resetCache();
this.getData();
}
}
Usage
Declare Cacheable<> object (presumably as part of the service):
list: Cacheable<string> = new Cacheable<string>();
and handler:
this.list.getHandler = () => {
// get data from server
return this.http.get(url)
.map((r: Response) => r.json() as string);
}
Call from a component:
//gets data from server
List.getData().subscribe(…)
You can have several components subscribed to it.
More details and code example are here: http://devinstance.net/articles/20171021/rxjs-cacheable
add a comment |
You can build simple class Cacheable<> that helps managing data retrieved from http server with multiple subscribers:
declare type GetDataHandler<T> = () => Observable<T>;
export class Cacheable<T> {
protected data: T;
protected subjectData: Subject<T>;
protected observableData: Observable<T>;
public getHandler: GetDataHandler<T>;
constructor() {
this.subjectData = new ReplaySubject(1);
this.observableData = this.subjectData.asObservable();
}
public getData(): Observable<T> {
if (!this.getHandler) {
throw new Error("getHandler is not defined");
}
if (!this.data) {
this.getHandler().map((r: T) => {
this.data = r;
return r;
}).subscribe(
result => this.subjectData.next(result),
err => this.subjectData.error(err)
);
}
return this.observableData;
}
public resetCache(): void {
this.data = null;
}
public refresh(): void {
this.resetCache();
this.getData();
}
}
Usage
Declare Cacheable<> object (presumably as part of the service):
list: Cacheable<string> = new Cacheable<string>();
and handler:
this.list.getHandler = () => {
// get data from server
return this.http.get(url)
.map((r: Response) => r.json() as string);
}
Call from a component:
//gets data from server
List.getData().subscribe(…)
You can have several components subscribed to it.
More details and code example are here: http://devinstance.net/articles/20171021/rxjs-cacheable
add a comment |
You can build simple class Cacheable<> that helps managing data retrieved from http server with multiple subscribers:
declare type GetDataHandler<T> = () => Observable<T>;
export class Cacheable<T> {
protected data: T;
protected subjectData: Subject<T>;
protected observableData: Observable<T>;
public getHandler: GetDataHandler<T>;
constructor() {
this.subjectData = new ReplaySubject(1);
this.observableData = this.subjectData.asObservable();
}
public getData(): Observable<T> {
if (!this.getHandler) {
throw new Error("getHandler is not defined");
}
if (!this.data) {
this.getHandler().map((r: T) => {
this.data = r;
return r;
}).subscribe(
result => this.subjectData.next(result),
err => this.subjectData.error(err)
);
}
return this.observableData;
}
public resetCache(): void {
this.data = null;
}
public refresh(): void {
this.resetCache();
this.getData();
}
}
Usage
Declare Cacheable<> object (presumably as part of the service):
list: Cacheable<string> = new Cacheable<string>();
and handler:
this.list.getHandler = () => {
// get data from server
return this.http.get(url)
.map((r: Response) => r.json() as string);
}
Call from a component:
//gets data from server
List.getData().subscribe(…)
You can have several components subscribed to it.
More details and code example are here: http://devinstance.net/articles/20171021/rxjs-cacheable
You can build simple class Cacheable<> that helps managing data retrieved from http server with multiple subscribers:
declare type GetDataHandler<T> = () => Observable<T>;
export class Cacheable<T> {
protected data: T;
protected subjectData: Subject<T>;
protected observableData: Observable<T>;
public getHandler: GetDataHandler<T>;
constructor() {
this.subjectData = new ReplaySubject(1);
this.observableData = this.subjectData.asObservable();
}
public getData(): Observable<T> {
if (!this.getHandler) {
throw new Error("getHandler is not defined");
}
if (!this.data) {
this.getHandler().map((r: T) => {
this.data = r;
return r;
}).subscribe(
result => this.subjectData.next(result),
err => this.subjectData.error(err)
);
}
return this.observableData;
}
public resetCache(): void {
this.data = null;
}
public refresh(): void {
this.resetCache();
this.getData();
}
}
Usage
Declare Cacheable<> object (presumably as part of the service):
list: Cacheable<string> = new Cacheable<string>();
and handler:
this.list.getHandler = () => {
// get data from server
return this.http.get(url)
.map((r: Response) => r.json() as string);
}
Call from a component:
//gets data from server
List.getData().subscribe(…)
You can have several components subscribed to it.
More details and code example are here: http://devinstance.net/articles/20171021/rxjs-cacheable
answered Nov 27 '17 at 4:26


yfranz
222212
222212
add a comment |
add a comment |
Great answers.
Or you could do this:
This is from latest version of rxjs. I am using 5.5.7 version of RxJS
import {share} from "rxjs/operators";
this.http.get('/someUrl').pipe(share());
add a comment |
Great answers.
Or you could do this:
This is from latest version of rxjs. I am using 5.5.7 version of RxJS
import {share} from "rxjs/operators";
this.http.get('/someUrl').pipe(share());
add a comment |
Great answers.
Or you could do this:
This is from latest version of rxjs. I am using 5.5.7 version of RxJS
import {share} from "rxjs/operators";
this.http.get('/someUrl').pipe(share());
Great answers.
Or you could do this:
This is from latest version of rxjs. I am using 5.5.7 version of RxJS
import {share} from "rxjs/operators";
this.http.get('/someUrl').pipe(share());
edited Jun 25 '18 at 8:52


candidJ
2,8911426
2,8911426
answered Mar 29 '18 at 15:54
Jay Modi
974
974
add a comment |
add a comment |
You could simply use ngx-cacheable! It better suits your scenario.
The benefit of using this
- It calls rest API only once, caches the response & returns the same for following requests.
- Can call API as required after create/ update/ delete operation.
So, Your service class would be something like this -
import { Injectable } from '@angular/core';
import { Cacheable, CacheBuster } from 'ngx-cacheable';
const customerNotifier = new Subject();
@Injectable()
export class customersService {
// relieves all its caches when any new value is emitted in the stream using notifier
@Cacheable({
cacheBusterObserver: customerNotifier,
async: true
})
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
// notifies the observer to refresh the data
@CacheBuster({
cacheBusterNotifier: customerNotifier
})
addCustomer() {
// some code
}
// notifies the observer to refresh the data
@CacheBuster({
cacheBusterNotifier: customerNotifier
})
updateCustomer() {
// some code
}
}
Here's the link for more reference.
add a comment |
You could simply use ngx-cacheable! It better suits your scenario.
The benefit of using this
- It calls rest API only once, caches the response & returns the same for following requests.
- Can call API as required after create/ update/ delete operation.
So, Your service class would be something like this -
import { Injectable } from '@angular/core';
import { Cacheable, CacheBuster } from 'ngx-cacheable';
const customerNotifier = new Subject();
@Injectable()
export class customersService {
// relieves all its caches when any new value is emitted in the stream using notifier
@Cacheable({
cacheBusterObserver: customerNotifier,
async: true
})
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
// notifies the observer to refresh the data
@CacheBuster({
cacheBusterNotifier: customerNotifier
})
addCustomer() {
// some code
}
// notifies the observer to refresh the data
@CacheBuster({
cacheBusterNotifier: customerNotifier
})
updateCustomer() {
// some code
}
}
Here's the link for more reference.
add a comment |
You could simply use ngx-cacheable! It better suits your scenario.
The benefit of using this
- It calls rest API only once, caches the response & returns the same for following requests.
- Can call API as required after create/ update/ delete operation.
So, Your service class would be something like this -
import { Injectable } from '@angular/core';
import { Cacheable, CacheBuster } from 'ngx-cacheable';
const customerNotifier = new Subject();
@Injectable()
export class customersService {
// relieves all its caches when any new value is emitted in the stream using notifier
@Cacheable({
cacheBusterObserver: customerNotifier,
async: true
})
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
// notifies the observer to refresh the data
@CacheBuster({
cacheBusterNotifier: customerNotifier
})
addCustomer() {
// some code
}
// notifies the observer to refresh the data
@CacheBuster({
cacheBusterNotifier: customerNotifier
})
updateCustomer() {
// some code
}
}
Here's the link for more reference.
You could simply use ngx-cacheable! It better suits your scenario.
The benefit of using this
- It calls rest API only once, caches the response & returns the same for following requests.
- Can call API as required after create/ update/ delete operation.
So, Your service class would be something like this -
import { Injectable } from '@angular/core';
import { Cacheable, CacheBuster } from 'ngx-cacheable';
const customerNotifier = new Subject();
@Injectable()
export class customersService {
// relieves all its caches when any new value is emitted in the stream using notifier
@Cacheable({
cacheBusterObserver: customerNotifier,
async: true
})
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
// notifies the observer to refresh the data
@CacheBuster({
cacheBusterNotifier: customerNotifier
})
addCustomer() {
// some code
}
// notifies the observer to refresh the data
@CacheBuster({
cacheBusterNotifier: customerNotifier
})
updateCustomer() {
// some code
}
}
Here's the link for more reference.
answered Nov 19 '18 at 14:26
Tushar Walzade
1,35321230
1,35321230
add a comment |
add a comment |
This is exactly what I have created the library ngx-rxcache for.
Take a look at it at https://github.com/adriandavidbrand/ngx-rxcache and see a working example at https://stackblitz.com/edit/angular-jxqaiv
add a comment |
This is exactly what I have created the library ngx-rxcache for.
Take a look at it at https://github.com/adriandavidbrand/ngx-rxcache and see a working example at https://stackblitz.com/edit/angular-jxqaiv
add a comment |
This is exactly what I have created the library ngx-rxcache for.
Take a look at it at https://github.com/adriandavidbrand/ngx-rxcache and see a working example at https://stackblitz.com/edit/angular-jxqaiv
This is exactly what I have created the library ngx-rxcache for.
Take a look at it at https://github.com/adriandavidbrand/ngx-rxcache and see a working example at https://stackblitz.com/edit/angular-jxqaiv
answered Dec 17 '18 at 2:22
Adrian Brand
3,26411019
3,26411019
add a comment |
add a comment |
Have you tried running the code you already have?
Because you are constructing the Observable from the promise resulting from getJSON()
, the network request is made before anyone subscribes. And the resulting promise is shared by all subscribers.
var promise = jQuery.getJSON(requestUrl); // network call is executed now
var o = Rx.Observable.fromPromise(promise); // just wraps it in an observable
o.subscribe(...); // does not trigger network call
o.subscribe(...); // does not trigger network call
// ...
i've edit the question to make it Angular 2 specific
– Angular University
Mar 29 '16 at 14:00
add a comment |
Have you tried running the code you already have?
Because you are constructing the Observable from the promise resulting from getJSON()
, the network request is made before anyone subscribes. And the resulting promise is shared by all subscribers.
var promise = jQuery.getJSON(requestUrl); // network call is executed now
var o = Rx.Observable.fromPromise(promise); // just wraps it in an observable
o.subscribe(...); // does not trigger network call
o.subscribe(...); // does not trigger network call
// ...
i've edit the question to make it Angular 2 specific
– Angular University
Mar 29 '16 at 14:00
add a comment |
Have you tried running the code you already have?
Because you are constructing the Observable from the promise resulting from getJSON()
, the network request is made before anyone subscribes. And the resulting promise is shared by all subscribers.
var promise = jQuery.getJSON(requestUrl); // network call is executed now
var o = Rx.Observable.fromPromise(promise); // just wraps it in an observable
o.subscribe(...); // does not trigger network call
o.subscribe(...); // does not trigger network call
// ...
Have you tried running the code you already have?
Because you are constructing the Observable from the promise resulting from getJSON()
, the network request is made before anyone subscribes. And the resulting promise is shared by all subscribers.
var promise = jQuery.getJSON(requestUrl); // network call is executed now
var o = Rx.Observable.fromPromise(promise); // just wraps it in an observable
o.subscribe(...); // does not trigger network call
o.subscribe(...); // does not trigger network call
// ...
answered Mar 29 '16 at 13:48
Brandon
27.1k45367
27.1k45367
i've edit the question to make it Angular 2 specific
– Angular University
Mar 29 '16 at 14:00
add a comment |
i've edit the question to make it Angular 2 specific
– Angular University
Mar 29 '16 at 14:00
i've edit the question to make it Angular 2 specific
– Angular University
Mar 29 '16 at 14:00
i've edit the question to make it Angular 2 specific
– Angular University
Mar 29 '16 at 14:00
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.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- 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%2f36271899%2fwhat-is-the-correct-way-to-share-the-result-of-an-angular-http-network-call-in-r%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
> share is identical to publish().refCount(). Actually it's not. See the following discussion: github.com/ReactiveX/rxjs/issues/1363
– Christian
Mar 29 '16 at 17:25
1
edited question, according to the issue looks like the docs on the code need to be updated -> github.com/ReactiveX/rxjs/blob/master/src/operator/share.ts
– Angular University
Mar 29 '16 at 17:49
I think 'it depends'. But for calls where you can't cache the data locally b/c it might not make sense due to parameters changing/combinations .share() seems to absolutely be the right thing. But if you can cache things locally some of the other answers regarding ReplaySubject/BehaviorSubject are also good solutions.
– JimB
Apr 25 '16 at 12:59
I think not only we need cache the data, we also need update/modify the data cached. It's a common case. For example, if I want to add a new field to the model cached or update the value of field. Maybe create a singleton DataCacheService with CRUD method is a better way? Like store of Redux. What do you think?
– slideshowp2
Sep 26 '17 at 3:26
You could simply use ngx-cacheable! It better suits your scenario. Refer my answer below
– Tushar Walzade
Nov 19 '18 at 14:07