Quelle est la manière correcte de partager le résultat d’un appel réseau angular Http dans RxJs 5?

En utilisant HTTP, nous appelons une méthode qui effectue un appel réseau et renvoie une observable http:

getCustomer() { return this.http.get('/someUrl').map(res => res.json()); } 

Si nous prenons cette observation et y ajoutons plusieurs abonnés:

 let network$ = getCustomer(); let subscriber1 = network$.subscribe(...); let subscriber2 = network$.subscribe(...); 

Ce que nous voulons faire, c’est de veiller à ce que cela ne provoque pas plusieurs demandes réseau.

Cela peut sembler un scénario inhabituel, mais c’est en fait assez courant: par exemple, si l’appelant s’abonne à l’observable pour afficher un message d’erreur et le transmet au modèle en utilisant le canal asynchrone, nous avons déjà deux abonnés.

Quelle est la bonne façon de le faire dans RxJs 5?

À savoir, cela semble fonctionner correctement:

 getCustomer() { return this.http.get('/someUrl').map(res => res.json()).share(); } 

Mais est-ce la manière idiomatique de le faire dans RxJs 5, ou devrions-nous faire autre chose à la place?

Note: Conformément à la nouvelle version de HttpClient Angular 5, la partie .map(res => res.json()) dans tous les exemples est maintenant inutile, car le résultat JSON est désormais supposé par défaut.

Mettez les données en cache et, si elles sont disponibles en cache, renvoyez-les sinon faites la requête HTTP.

 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:ssortingng = 'https://cors-test.appspot.com/test'; private data: Data; private observable: Observable; 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; } } } 

Exemple Plunker

Cette artile https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html est une excellente explication pour mettre en cache avec shareReplay .

Selon la suggestion de @Cristian, c’est un moyen qui fonctionne bien pour les observables HTTP, qui n’émettent qu’une seule fois et se terminent ensuite:

 getCustomer() { return this.http.get('/someUrl') .map(res => res.json()).publishLast().refCount(); } 

MISE À JOUR: Ben Lesh dit la prochaine version mineure après 5.2.0, vous pourrez simplement appeler shareReplay () pour le mettre en cache.

PRÉCÉDEMMENT…..

Tout d’abord, n’utilisez pas share () ou publishReplay (1) .refCount (), ils sont identiques et le problème, c’est qu’il ne partage que si les connexions sont effectuées alors que l’observable est active, si vous vous connectez une fois terminé. , il crée à nouveau une nouvelle observable, la traduction, pas vraiment la mise en cache.

Birowski a donné la bonne solution ci-dessus, qui consiste à utiliser ReplaySubject. ReplaySubject mettra en cache dans notre cas les valeurs que vous lui donnez (bufferSize).

Voici une fonction réutilisable

 export function cacheable(o: Observable): Observable { let replay = new ReplaySubject(1); o.subscribe( x => replay.next(x), x => replay.error(x), () => replay.complete() ); return replay.asObservable(); } 

Voici comment l’utiliser

 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; constructor(private _http: Http, ) { } refresh = () => { if (this._cache) { return this._cache; } return this._cache = cacheable(this._http.get('YOUR URL')); } } 

Vous trouverez ci-dessous une version plus avancée de la fonction de mise en cache. Celle-ci permet d’avoir sa propre table de correspondance + la possibilité de fournir une table de recherche personnalisée. De cette façon, vous n’avez pas à vérifier this._cache comme dans l’exemple ci-dessus. Notez également qu’au lieu de passer l’observable en tant que premier argument, vous passez une fonction qui retourne les observables, car le HTTP de Angular s’exécute immédiatement, donc en retournant une fonction exécutée paresseuse, nous pouvons décider de ne pas l’appeler si elle est déjà présente. notre cache

 let cacheableCache: { [key: ssortingng]: Observable } = {}; export function cacheable(returnObservable: () => Observable, key?: ssortingng, customCache?: { [key: ssortingng]: Observable }): Observable { if (!!key && (customCache || cacheableCache)[key]) { return (customCache || cacheableCache)[key] as Observable; } let replay = new ReplaySubject(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") 

selon cet article

Il s’avère que nous pouvons facilement append la mise en cache à l’observable en ajoutant publishReplay (1) et refCount.

donc à l’ intérieur si les déclarations viennent juste d’append

 .publishReplay(1) .refCount(); 

to .map(...)

rxjs 5.4.0 a une nouvelle méthode shareReplay .

  • rx-book shareReplay ()
  • Aucun document sur reactivex.io/rxjs

L’auteur dit explicitement “idéal pour gérer des choses comme la mise en cache des résultats AJAX”

rxjs PR # 2443 feat (shareReplay): ajoute la variante publishReplay de publishReplay

shareReplay renvoie une observable correspondant à la source multidiffusion sur un object ReplaySubject. Ce sujet de relecture est recyclé en cas d’erreur de la source, mais pas à la fin de la source. Cela rend shareReplay idéal pour gérer des choses comme la mise en cache des résultats AJAX, car il est possible de réessayer. Son comportement de répétition, cependant, diffère du partage en ce sens qu’il ne répètera pas la source observable, mais plutôt de répéter les valeurs de l’observable source.

J’ai joué la question, mais j’essaierai d’y aller.

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

Voici la preuve 🙂

Il n’y a qu’un seul getCustomer().subscribe(customer$) : getCustomer().subscribe(customer$)

Nous ne souscrivons pas à la réponse api de getCustomer() , nous sums abonnés à un object ReplaySubject qui est observable et qui est également capable de s’abonner à un object Observable différent et (et cela est important) de conserver sa dernière valeur émise et de la republier à c’est les abonnés de (ReplaySubject).

L’implémentation choisie dépendra si vous souhaitez que unsubscribe () annule ou non votre requête HTTP.

Dans tous les cas, les décorateurs de TypeScript sont un bon moyen de standardiser le comportement. C’est celui que j’ai écrit:

  @CacheObservableArgsKey getMyThing(id: ssortingng): Observable { return this.http.get('things/'+id); } 

Définition du décorateur:

 /** * 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: ssortingng, descriptor: PropertyDescriptor) { const originalFunc = descriptor.value; const cacheMap = new Map(); 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; } 

J’ai trouvé un moyen de stocker le résultat get http dans sessionStorage et de l’utiliser pour la session, afin qu’il n’appelle plus jamais le serveur.

Je l’ai utilisé pour appeler l’API github afin d’éviter les limites d’utilisation.

 @Injectable() export class HttpCache { constructor(private http: Http) {} get(url: ssortingng): Observable { 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 est de 5M (ou 4.75M). Donc, il ne devrait pas être utilisé comme ça pour un grand dataset.

—— modifier ————-
Si vous souhaitez avoir des données actualisées avec F5, qui utilise des données mémoire au lieu de sessionStorage;

 @Injectable() export class HttpCache { cached: any = {}; // this will store data constructor(private http: Http) {} get(url: ssortingng): Observable { 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(); }); } } } 

Cacheable HTTP Response Data à l’aide de Rxjs Observer / Observable + Caching + Subscription

Voir le code ci-dessous

* avertissement: je suis nouveau sur rxjs, alors gardez à l’esprit que je peux être abuser de l’approche observable / observateur. Ma solution est purement un conglomérat d’autres solutions que j’ai trouvées, et c’est la conséquence d’avoir échoué à trouver une solution simple et bien documentée. Ainsi, je fournis ma solution complète de code (comme j’aurais aimé trouver) dans l’espoir qu’elle aide les autres.

* Notez que cette approche est basée sur GoogleFirebaseObservables. Malheureusement, je ne dispose pas de l’expérience / du temps nécessaire pour reproduire ce qu’ils ont fait sous le capot. Mais ce qui suit est un moyen simpliste de fournir un access asynchrone à certaines données pouvant être mises en cache.

Situation : un composant “liste de produits” est chargé d’afficher une liste de produits. Le site est une application Web d’une page avec des boutons de menu qui filtreront les produits affichés sur la page.

Solution : Le composant “s’abonne” à une méthode de service. La méthode de service renvoie un tableau d’objects de produit auquel le composant accède via le rappel d’abonnement. La méthode de service encapsule son activité dans un observateur nouvellement créé et renvoie l’observateur. À l’intérieur de cet observateur, il recherche les données mises en cache et les renvoie à l’abonné (le composant) et les renvoie. Sinon, il lance un appel http pour récupérer les données, s’abonne à la réponse, où vous pouvez traiter ces données (par exemple, mapper les données sur votre propre modèle), puis renvoyer les données à l’abonné.

Le code

liste de produits.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{ //wrap getProducts around an Observable to make it async. let productsObservable$ = Observable.create((observer: Observer) => { //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 (le modèle)

 export interface ProductResponse { success: boolean; msg: ssortingng; products: Product[]; } export class Product { product_id: number; sku: ssortingng; product_title: ssortingng; ..etc... constructor(product_id: number, sku: ssortingng, product_title: ssortingng, ...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 ensortinges and constructor static fromJson({ product_id, sku, product_title, ...etc... }): Product { return new Product( product_id, sku, product_title, ...etc... ); } } 

Voici un exemple du résultat que je vois lorsque je charge la page dans Chrome. Notez que lors du chargement initial, les produits sont récupérés à partir de http (appel à mon service de repos de noeud, qui s’exécute localement sur le port 3000). Lorsque je clique ensuite sur pour accéder à une vue «filtrée» des produits, les produits sont trouvés dans le cache.

Mon journal Chrome (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 

… [cliqué sur un bouton de menu pour filtrer les produits] …

 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: C’est le moyen le plus simple que j’ai trouvé (jusqu’à présent) pour implémenter des données de réponse HTTP pouvant être mises en cache. Dans mon application angular, chaque fois que je navigue vers une vue différente des produits, le composant liste de produits se recharge. ProductService semble être une instance partagée, de sorte que le cache local de «produits: Product []» dans ProductService est conservé pendant la navigation, et les appels suivants à «GetProducts ()» renvoie la valeur mise en cache. Une dernière note, j’ai lu des commentaires sur la façon dont les observables / abonnements doivent être fermés lorsque vous avez terminé pour éviter les «memory leaks». Je n’ai pas inclus ceci ici, mais c’est quelque chose à garder à l’esprit.

Je suppose que @ ngx-cache / core pourrait être utile pour maintenir les fonctionnalités de mise en cache pour les appels HTTP, en particulier si l’appel HTTP est effectué à la fois sur les plates-formes de navigateur et de serveur .

Disons que nous avons la méthode suivante:

 getCustomer() { return this.http.get('/someUrl').map(res => res.json()); } 

Vous pouvez utiliser le décorateur Cached de @ ngx-cache / core pour stocker la valeur renvoyée à partir de la méthode faisant l’appel HTTP au niveau du cache storage ( le storage peut être configurable, veuillez vérifier l’implémentation à ng-seed / universal ) – la première exécution. La prochaine fois que la méthode est appelée (peu importe le navigateur ou la plate-forme du serveur ), la valeur est extraite du cache storage du 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()); } 

Il est également possible d’utiliser les méthodes de mise en cache ( has , get , set ) à l’aide de l’ API de mise en cache .

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 resortingeve 'some ssortingng value' getSomeSsortingngValue(): ssortingng { if (this.cache.has('some-ssortingng')) return this.cache.get('some-ssortingng'); this.cache.set('some-ssortingng', 'some ssortingng value'); return 'some ssortingng value'; } } 

Voici la liste des packages, à la fois pour la mise en cache côté client et côté serveur:

  • @ ngx-cache / core : utilitaire de cache
  • @ ngx-cache / platform-browser : Implémentation de la plate-forme SPA / Browser
  • @ ngx-cache / platform-server : implémentation de la plate-forme serveur
  • @ ngx-cache / fs-storage : utilitaire de stockage (requirejs pour la plate-forme serveur)

rxjs 5.3.0

Je ne suis pas satisfait de .map(myFunction).publishReplay(1).refCount()

Avec plusieurs abonnés, .map() exécute myFunction deux fois dans certains cas (je m’attends à ce qu’il ne s’exécute qu’une seule fois). Un correctif semble être publishReplay(1).refCount().take(1)

Une autre chose que vous pouvez faire est de ne pas utiliser refCount() et de rendre l’Observable tout de suite:

 let obs = this.http.get('my/data.json').publishReplay(1); obs.connect(); return obs; 

Cela démarrera la requête HTTP indépendamment des abonnés. Je ne suis pas sûr si le désabonnement avant que HTTP GET se termine annulera ou non.

Ce que nous voulons faire, c’est de veiller à ce que cela ne provoque pas plusieurs requêtes réseau.

Mon préféré est d’utiliser des méthodes async pour les appels qui font des requêtes réseau. Les méthodes elles-mêmes ne renvoient pas de valeur. Au lieu de cela, elles mettent à jour un object BehaviorSubject dans le même service, auquel les composants seront abonnés.

Maintenant, pourquoi utiliser un object BehaviorSubject au lieu d’un object Observable ? Car,

  • À la souscription, BehaviorSubject renvoie la dernière valeur, tandis qu’une observable régulière ne se déclenche que lors de la réception d’un onnext .
  • Si vous souhaitez récupérer la dernière valeur du BehaviorSubject dans un code non observable (sans abonnement), vous pouvez utiliser la méthode getValue() .

Exemple:

customer.service.ts

 public customers$: BehaviorSubject = new BehaviorSubject([]); public async getCustomers(): Promise { let customers = await this.httpClient.post(this.endPoint, criteria).toPromise(); if (customers) this.customers$.next(customers); } 

Ensuite, chaque fois que nécessaire, nous pouvons simplement nous abonner aux customers$ .

 public ngOnInit(): void { this.customerService.customers$ .subscribe((customers: Customer[]) => this.customerList = customers); } 

Ou peut-être que vous voulez l’utiliser directement dans un modèle

 
  • ...
  • Donc, jusqu’à ce que vous fassiez un autre appel à getCustomers , les données sont conservées dans le customers$ BehaviorSubject.

    Alors, que faire si vous souhaitez actualiser ces données? faites juste un appel à getCustomers()

     public async refresh(): Promise { try { await this.customerService.getCustomers(); } catch (e) { // request failed, handle exception console.error(e); } } 

    En utilisant cette méthode, nous n’avons pas besoin de conserver explicitement les données entre les appels réseau suivants, car elles sont gérées par le BehaviorSubject .

    PS: Habituellement, lorsqu’un composant est détruit, il est recommandé de supprimer les abonnements, pour cela vous pouvez utiliser la méthode suggérée dans cette réponse.

    Appelez simplement share () après map et avant tout abonnement .

    Dans mon cas, j’ai un service générique (RestClientService.ts) qui effectue le rest de l’appel, extrait des données, vérifie les erreurs et renvoie les données observables à un service d’implémentation concret (f.ex .: ContractClientService.ts), enfin cette implémentation concrète renvoie observable à de ContractComponent.ts, et celui-ci s’abonne pour mettre à jour la vue.

    RestClientService.ts:

     export abstract class RestClientService { public GetAll = (path: ssortingng, property: ssortingng): Observable => { 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: ssortingng) { ... } private handleError2(error: any, method: ssortingng, path: ssortingng) { ... } } 

    ContractService.ts:

     export class ContractService extends RestClientService { private GET_ALL_ITEMS_REST_URI_PATH = "search"; private GET_ALL_ITEMS_PROPERTY_PATH = "contract"; public getAllItems(): Observable { 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; }); } } 

    J’ai écrit une classe de cache,

     /** * 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 = new Map(); static maxItems: number = 400; static get(key: ssortingng){ return this.cachedData.get(key); } static getOrFetch(key: ssortingng, fetcher: (ssortingng) => 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; 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(); } } 

    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: ssortingng): Observable { 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 = 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 sortingcks but this was just fine for my purposes.

    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; */ private dataObserver:Array=[]; private loading:Object={}; private links:Object={}; counter:number=-1; constructor (private http: Http) { } private loadPostCache(link:ssortingng){ 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: ssortingng; if (error instanceof Response) { const body = error.json() || ''; const err = body.error || JSON.ssortingngify(body); errMsg = `${error.status} - ${error.statusText || ''} ${err}`; } else { errMsg = error.message ? error.message : error.toSsortingng(); } console.error(errMsg); return Observable.throw(errMsg); } postCache(link:ssortingng): Observable{ 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]); } } ); } }); } } 

    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: ssortingng = 'RestResource_Loading'; static readonly ERROR: ssortingng = 'RestResource_Error'; static readonly IDLE: ssortingng = 'RestResource_Idle'; public value: Observable; public status: Observable; private loadStatus: Observer; private reloader: Observable; private reloadTrigger: Observer; constructor(requestObservableFn: () => Observable) { this.status = Observable.create((o) => { this.loadStatus = o; }); this.reloader = Observable.create((o: Observer) => { 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); } } 

    You can build simple class Cacheable<> that helps managing data retrieved from http server with multiple subscribers:

     declare type GetDataHandler = () => Observable; export class Cacheable { protected data: T; protected subjectData: Subject; protected observableData: Observable; public getHandler: GetDataHandler; constructor() { this.subjectData = new ReplaySubject(1); this.observableData = this.subjectData.asObservable(); } public getData(): Observable { 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 = new Cacheable(); 

    and handler:

     this.list.getHandler = () => { // get data from server return this.http.get(url) .map((r: Response) => r.json() as ssortingng[]); } 

    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

    Bonnes réponses

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

    Have you sortinged 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 sortinggger network call o.subscribe(...); // does not sortinggger network call // ...