What is the correct way to share the result of an Angular Http network call in RxJs 5?












241














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.










share|improve this question




















  • 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
















241














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.










share|improve this question




















  • 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














241












241








241


123





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.










share|improve this question















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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














  • 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












21 Answers
21






active

oldest

votes


















196














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.






share|improve this answer



















  • 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








  • 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








  • 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






  • 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



















40














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();
}





share|improve this answer























  • 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




    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



















29














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")





share|improve this answer































    22














    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(...)






    share|improve this answer































      19














      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.







      share|improve this answer























      • 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



















      7














      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.






      share|improve this answer



















      • 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



















      4














      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;
      }





      share|improve this answer























      • 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



















      4














      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();
      });
      }
      }
      }





      share|improve this answer























      • 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 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












      • 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





















      3














      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.






      share|improve this answer



















      • 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



















      2














      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)






      share|improve this answer





























        1














        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.






        share|improve this answer





























          1















          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.






          share|improve this answer





























            0














            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;
            });
            }

            }





            share|improve this answer





























              0














              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.






              share|improve this answer





























                0














                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]);
                }
                }
                );
                }
                });
                }
                }





                share|improve this answer





























                  0














                  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);
                  }

                  }





                  share|improve this answer





























                    0














                    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






                    share|improve this answer





























                      0














                      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());





                      share|improve this answer































                        0














                        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.






                        share|improve this answer





























                          0














                          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






                          share|improve this answer





























                            -2














                            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
                            // ...





                            share|improve this answer





















                            • i've edit the question to make it Angular 2 specific
                              – Angular University
                              Mar 29 '16 at 14:00













                            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
                            });


                            }
                            });














                            draft saved

                            draft discarded


















                            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









                            196














                            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.






                            share|improve this answer



















                            • 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








                            • 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








                            • 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






                            • 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
















                            196














                            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.






                            share|improve this answer



















                            • 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








                            • 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








                            • 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






                            • 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














                            196












                            196








                            196






                            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.






                            share|improve this answer














                            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.







                            share|improve this answer














                            share|improve this answer



                            share|improve this answer








                            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 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




                              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




                              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




                              @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




                              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




                              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




                              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




                              @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













                            40














                            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();
                            }





                            share|improve this answer























                            • 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




                              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
















                            40














                            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();
                            }





                            share|improve this answer























                            • 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




                              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














                            40












                            40








                            40






                            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();
                            }





                            share|improve this answer














                            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();
                            }






                            share|improve this answer














                            share|improve this answer



                            share|improve this answer








                            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 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




                              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


















                            • 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




                              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
















                            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











                            29














                            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")





                            share|improve this answer




























                              29














                              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")





                              share|improve this answer


























                                29












                                29








                                29






                                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")





                                share|improve this answer














                                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")






                                share|improve this answer














                                share|improve this answer



                                share|improve this answer








                                edited Aug 17 '17 at 7:03









                                taras-d

                                902216




                                902216










                                answered Mar 23 '17 at 1:28









                                Guojian Miguel Wu

                                29134




                                29134























                                    22














                                    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(...)






                                    share|improve this answer




























                                      22














                                      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(...)






                                      share|improve this answer


























                                        22












                                        22








                                        22






                                        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(...)






                                        share|improve this answer














                                        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(...)







                                        share|improve this answer














                                        share|improve this answer



                                        share|improve this answer








                                        edited Jul 7 '16 at 15:50









                                        Günter Zöchbauer

                                        314k65932877




                                        314k65932877










                                        answered Jun 10 '16 at 20:17









                                        Ivanesses

                                        23728




                                        23728























                                            19














                                            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.







                                            share|improve this answer























                                            • 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
















                                            19














                                            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.







                                            share|improve this answer























                                            • 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














                                            19












                                            19








                                            19






                                            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.







                                            share|improve this answer














                                            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.








                                            share|improve this answer














                                            share|improve this answer



                                            share|improve this answer








                                            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


















                                            • 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











                                            7














                                            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.






                                            share|improve this answer



















                                            • 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
















                                            7














                                            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.






                                            share|improve this answer



















                                            • 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














                                            7












                                            7








                                            7






                                            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.






                                            share|improve this answer














                                            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.







                                            share|improve this answer














                                            share|improve this answer



                                            share|improve this answer








                                            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














                                            • 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











                                            4














                                            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;
                                            }





                                            share|improve this answer























                                            • 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
















                                            4














                                            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;
                                            }





                                            share|improve this answer























                                            • 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














                                            4












                                            4








                                            4






                                            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;
                                            }





                                            share|improve this answer














                                            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;
                                            }






                                            share|improve this answer














                                            share|improve this answer



                                            share|improve this answer








                                            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 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
















                                            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











                                            4














                                            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();
                                            });
                                            }
                                            }
                                            }





                                            share|improve this answer























                                            • 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 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












                                            • 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


















                                            4














                                            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();
                                            });
                                            }
                                            }
                                            }





                                            share|improve this answer























                                            • 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 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












                                            • 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
















                                            4












                                            4








                                            4






                                            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();
                                            });
                                            }
                                            }
                                            }





                                            share|improve this answer














                                            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();
                                            });
                                            }
                                            }
                                            }






                                            share|improve this answer














                                            share|improve this answer



                                            share|improve this answer








                                            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 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












                                            • 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




















                                            • 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 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












                                            • 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


















                                            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













                                            3














                                            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.






                                            share|improve this answer



















                                            • 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
















                                            3














                                            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.






                                            share|improve this answer



















                                            • 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














                                            3












                                            3








                                            3






                                            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.






                                            share|improve this answer














                                            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.







                                            share|improve this answer














                                            share|improve this answer



                                            share|improve this answer








                                            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














                                            • 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











                                            2














                                            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)






                                            share|improve this answer


























                                              2














                                              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)






                                              share|improve this answer
























                                                2












                                                2








                                                2






                                                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)






                                                share|improve this answer












                                                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)







                                                share|improve this answer












                                                share|improve this answer



                                                share|improve this answer










                                                answered May 3 '17 at 7:03









                                                Burak Tasci

                                                627816




                                                627816























                                                    1














                                                    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.






                                                    share|improve this answer


























                                                      1














                                                      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.






                                                      share|improve this answer
























                                                        1












                                                        1








                                                        1






                                                        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.






                                                        share|improve this answer












                                                        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.







                                                        share|improve this answer












                                                        share|improve this answer



                                                        share|improve this answer










                                                        answered Apr 22 '17 at 0:26









                                                        Arlo

                                                        7381818




                                                        7381818























                                                            1















                                                            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.






                                                            share|improve this answer


























                                                              1















                                                              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.






                                                              share|improve this answer
























                                                                1












                                                                1








                                                                1







                                                                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.






                                                                share|improve 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.







                                                                share|improve this answer












                                                                share|improve this answer



                                                                share|improve this answer










                                                                answered Mar 24 '18 at 1:36









                                                                cyberpirate92

                                                                1,36221529




                                                                1,36221529























                                                                    0














                                                                    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;
                                                                    });
                                                                    }

                                                                    }





                                                                    share|improve this answer


























                                                                      0














                                                                      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;
                                                                      });
                                                                      }

                                                                      }





                                                                      share|improve this answer
























                                                                        0












                                                                        0








                                                                        0






                                                                        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;
                                                                        });
                                                                        }

                                                                        }





                                                                        share|improve this answer












                                                                        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;
                                                                        });
                                                                        }

                                                                        }






                                                                        share|improve this answer












                                                                        share|improve this answer



                                                                        share|improve this answer










                                                                        answered May 23 '16 at 9:43









                                                                        surfealokesea

                                                                        2,47111829




                                                                        2,47111829























                                                                            0














                                                                            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.






                                                                            share|improve this answer


























                                                                              0














                                                                              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.






                                                                              share|improve this answer
























                                                                                0












                                                                                0








                                                                                0






                                                                                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.






                                                                                share|improve this answer












                                                                                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.







                                                                                share|improve this answer












                                                                                share|improve this answer



                                                                                share|improve this answer










                                                                                answered Dec 16 '16 at 14:40









                                                                                Ondra Žižka

                                                                                19.8k23135209




                                                                                19.8k23135209























                                                                                    0














                                                                                    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]);
                                                                                    }
                                                                                    }
                                                                                    );
                                                                                    }
                                                                                    });
                                                                                    }
                                                                                    }





                                                                                    share|improve this answer


























                                                                                      0














                                                                                      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]);
                                                                                      }
                                                                                      }
                                                                                      );
                                                                                      }
                                                                                      });
                                                                                      }
                                                                                      }





                                                                                      share|improve this answer
























                                                                                        0












                                                                                        0








                                                                                        0






                                                                                        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]);
                                                                                        }
                                                                                        }
                                                                                        );
                                                                                        }
                                                                                        });
                                                                                        }
                                                                                        }





                                                                                        share|improve this answer












                                                                                        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]);
                                                                                        }
                                                                                        }
                                                                                        );
                                                                                        }
                                                                                        });
                                                                                        }
                                                                                        }






                                                                                        share|improve this answer












                                                                                        share|improve this answer



                                                                                        share|improve this answer










                                                                                        answered Jan 23 '17 at 10:52









                                                                                        Ravinder Payal

                                                                                        1,3591228




                                                                                        1,3591228























                                                                                            0














                                                                                            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);
                                                                                            }

                                                                                            }





                                                                                            share|improve this answer


























                                                                                              0














                                                                                              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);
                                                                                              }

                                                                                              }





                                                                                              share|improve this answer
























                                                                                                0












                                                                                                0








                                                                                                0






                                                                                                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);
                                                                                                }

                                                                                                }





                                                                                                share|improve this answer












                                                                                                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);
                                                                                                }

                                                                                                }






                                                                                                share|improve this answer












                                                                                                share|improve this answer



                                                                                                share|improve this answer










                                                                                                answered Nov 14 '17 at 22:40









                                                                                                Matjaz Hirsman

                                                                                                913




                                                                                                913























                                                                                                    0














                                                                                                    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






                                                                                                    share|improve this answer


























                                                                                                      0














                                                                                                      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






                                                                                                      share|improve this answer
























                                                                                                        0












                                                                                                        0








                                                                                                        0






                                                                                                        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






                                                                                                        share|improve this answer












                                                                                                        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







                                                                                                        share|improve this answer












                                                                                                        share|improve this answer



                                                                                                        share|improve this answer










                                                                                                        answered Nov 27 '17 at 4:26









                                                                                                        yfranz

                                                                                                        222212




                                                                                                        222212























                                                                                                            0














                                                                                                            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());





                                                                                                            share|improve this answer




























                                                                                                              0














                                                                                                              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());





                                                                                                              share|improve this answer


























                                                                                                                0












                                                                                                                0








                                                                                                                0






                                                                                                                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());





                                                                                                                share|improve this answer














                                                                                                                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());






                                                                                                                share|improve this answer














                                                                                                                share|improve this answer



                                                                                                                share|improve this answer








                                                                                                                edited Jun 25 '18 at 8:52









                                                                                                                candidJ

                                                                                                                2,8911426




                                                                                                                2,8911426










                                                                                                                answered Mar 29 '18 at 15:54









                                                                                                                Jay Modi

                                                                                                                974




                                                                                                                974























                                                                                                                    0














                                                                                                                    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.






                                                                                                                    share|improve this answer


























                                                                                                                      0














                                                                                                                      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.






                                                                                                                      share|improve this answer
























                                                                                                                        0












                                                                                                                        0








                                                                                                                        0






                                                                                                                        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.






                                                                                                                        share|improve this answer












                                                                                                                        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.







                                                                                                                        share|improve this answer












                                                                                                                        share|improve this answer



                                                                                                                        share|improve this answer










                                                                                                                        answered Nov 19 '18 at 14:26









                                                                                                                        Tushar Walzade

                                                                                                                        1,35321230




                                                                                                                        1,35321230























                                                                                                                            0














                                                                                                                            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






                                                                                                                            share|improve this answer


























                                                                                                                              0














                                                                                                                              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






                                                                                                                              share|improve this answer
























                                                                                                                                0












                                                                                                                                0








                                                                                                                                0






                                                                                                                                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






                                                                                                                                share|improve this answer












                                                                                                                                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







                                                                                                                                share|improve this answer












                                                                                                                                share|improve this answer



                                                                                                                                share|improve this answer










                                                                                                                                answered Dec 17 '18 at 2:22









                                                                                                                                Adrian Brand

                                                                                                                                3,26411019




                                                                                                                                3,26411019























                                                                                                                                    -2














                                                                                                                                    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
                                                                                                                                    // ...





                                                                                                                                    share|improve this answer





















                                                                                                                                    • i've edit the question to make it Angular 2 specific
                                                                                                                                      – Angular University
                                                                                                                                      Mar 29 '16 at 14:00


















                                                                                                                                    -2














                                                                                                                                    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
                                                                                                                                    // ...





                                                                                                                                    share|improve this answer





















                                                                                                                                    • i've edit the question to make it Angular 2 specific
                                                                                                                                      – Angular University
                                                                                                                                      Mar 29 '16 at 14:00
















                                                                                                                                    -2












                                                                                                                                    -2








                                                                                                                                    -2






                                                                                                                                    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
                                                                                                                                    // ...





                                                                                                                                    share|improve this answer












                                                                                                                                    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
                                                                                                                                    // ...






                                                                                                                                    share|improve this answer












                                                                                                                                    share|improve this answer



                                                                                                                                    share|improve this answer










                                                                                                                                    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




















                                                                                                                                    • 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




















                                                                                                                                    draft saved

                                                                                                                                    draft discarded




















































                                                                                                                                    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.




                                                                                                                                    draft saved


                                                                                                                                    draft discarded














                                                                                                                                    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





















































                                                                                                                                    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







                                                                                                                                    Popular posts from this blog

                                                                                                                                    MongoDB - Not Authorized To Execute Command

                                                                                                                                    How to fix TextFormField cause rebuild widget in Flutter

                                                                                                                                    in spring boot 2.1 many test slices are not allowed anymore due to multiple @BootstrapWith