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

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


EDIT: as of 2021, the proper way is to use the shareReplay operator natively proposed by RxJs. See more details in below answers.


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' operatorimport '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 article https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html is a great explanation how to cache with shareReplay.


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


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