Quelle est la bonne façon de partager le résultat D'un appel réseau HTTP angulaire dans RxJs 5?

en utilisant Http, nous appelons une méthode qui fait un appel réseau et renvoie un http observable:

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

si nous prenons cette observable et y ajoutons plusieurs abonnés:

let network$ = getCustomer();

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

ce que nous voulons faire, c'est nous assurer que cela ne provoque pas de multiples requêtes réseau.

Cela peut sembler comme un scénario inhabituel, mais son en fait assez commun: par exemple si l'appelant s'abonne à la observables pour affichez un message d'erreur, et le transmet au modèle en utilisant le tuyau async, nous avons déjà deux abonnés.

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

à savoir, cela semble bien fonctionner:

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

mais est - ce la façon idiomatique de faire cela dans RxJs 5, ou devrions-nous faire quelque chose d'autre à la place?

Note: conformément à L'Angle 5 Nouveau HttpClient , la partie .map(res => res.json()) tous les exemples sont maintenant inutiles, puisque le résultat JSON est maintenant supposé par défaut.

228
demandé sur Pac0 2016-03-29 00:55:35

19 réponses

Cache les données et si elles sont disponibles, renvoie 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: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;
    }
  }
}

"exemple de plongeur

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

185
répondu Günter Zöchbauer 2018-08-31 09:01:45

suggestion de Per @Cristian, c'est une façon qui fonctionne bien pour les observables HTTP, qui n'émettent qu'une fois et puis ils complètent:

getCustomer() {
    return this.http.get('/someUrl')
        .map(res => res.json()).publishLast().refCount();
}
38
répondu Angular University 2017-03-23 15:50:54

mise à jour: Ben Lesh dit que la prochaine version mineure après 5.2.0, vous pourrez simplement appeler shareReplay() pour vraiment mettre en cache.

précédemment.....

premièrement, n'utilisez pas share () ou publishReplay (1).refCount (), ils sont les mêmes et le problème avec lui, c'est qu'il ne partage que si les connexions sont faites pendant que l'observable est actif, si vous vous connectez après qu'il soit terminé, il crée à nouveau un nouvel observable, la traduction, pas vraiment la mise en cache.

Birowski a donné la bonne solution ci-dessus, qui est d'utiliser ReplaySubject. ReplaySubject cache les valeurs que vous lui donnez (bufferSize) dans notre cas 1. Il ne créera pas de nouveau observable comme share() une fois que refCount atteint zéro et que vous faites une nouvelle connexion, ce qui est le bon comportement pour la mise en cache.

Voici une fonction réutilisable

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

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<any>;
  constructor(private _http: Http, ) { }

  refresh = () => {
    if (this._cache) {
      return this._cache;
    }
    return this._cache = cacheable<any>(this._http.get('YOUR URL'));
  }
}

ci-dessous est une version plus avancée de la fonction de mise en cache que celui-ci permet a sa propre table de recherche + la capacité de fournir une table de recherche personnalisée. De cette façon, vous n'avez pas à vérifier ce point._cache comme dans l'exemple ci-dessus. Notez aussi qu'au lieu de passer l'observable comme premier argument, vous passez une fonction qui renvoie les observables, c'est parce que le Http D'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à dans notre 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;
}

Utilisation:

getData() => cacheable(this._http.get("YOUR URL"), "this is key for my cache")
26
répondu Guojian Miguel Wu 2017-08-17 07:03:06

selon ce article

il s'avère que nous pouvons facilement ajouter la mise en cache à l'observable en ajoutant publireplay(1) et refCount.

donc à l'intérieur si les déclarations ajoutez simplement

.publishReplay(1)
.refCount();

à .map(...)

20
répondu Ivanesses 2016-07-07 15:50:43

rxjs 5.4.0 a une nouvelle shareReplay méthode.

L'auteur dit explicitement "idéal pour la manutention des choses comme la mise en cache AJAX" résultats

RxJS PR #2443 exploit(shareReplay): ajoute shareReplay variante de publishReplay

shareReplay renvoie une observable qui est la source multicastée sur a ReplaySubject. Relecture sujet est recyclé sur l'erreur de la source, mais pas une fois la source terminée. Cela rend shareReplay idéal pour gérer des choses comme la mise en cache des résultats AJAX, car il est renouvelable. C'est le comportement de répétition, cependant, diffère de la part dans que il ne répétera pas la source observable, il répétera plutôt le source valeurs observables.

17
répondu Arlo 2017-09-13 17:24:46

c'est moi qui ai posé la question, mais je vais essayer.

//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 plat à emporter: getCustomer().subscribe(customer$)

nous ne souscrivons pas à la réponse api de getCustomer() , nous souscrivons à un ReplaySubject qui est observable qui est également capable de souscrire à un Observable différent et (et c'est important) de tenir sa dernière valeur émise et republiez-le à l'un de ses abonnés(Replaysubject's).

6
répondu Birowsky 2018-01-21 06:42:53

l'implémentation que vous choisissez dépendra de si vous voulez vous désabonner() pour annuler votre requête HTTP ou non.

dans tous les cas, décorateurs typographiques sont une bonne façon de normaliser le comportement. C'est celui que j'ai écrit:

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

décorateur définition:

/**
 * 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;
}
4
répondu Arlo 2018-01-20 23:17:41

j'ai trouvé un moyen de stocker le résultat http get dans sessionStorage et de l'utiliser pour la session, de sorte qu'il n'appellera plus jamais le serveur.

Je l'ai utilisé pour appeler L'API GitHub pour éviter la limite d'utilisation.

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

POUR INFORMATION, la limite de sessionStorage est de 5M (ou 4,75 M). Donc, il ne devrait pas être utilisé comme cela, pour grand ensemble de données.

------ modifier -------------

Si vous voulez avoir des données mises à jour avec F5, qui utilise des donnéesmémorales au lieu de 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();
        });
    }
  }
}
4
répondu allenhwkim 2018-03-31 23:53:05

mettre en Cache la Réponse HTTP de Données à l'aide de Rxjs Observateur/Observable + Cache + Abonnement

Voir Code Ci-Dessous

*avertissement: je suis nouveau à rxjs, donc garder à l'esprit que je suis peut-être le mauvais usage des observables/observateur approche. Ma solution est purement un conglomérat d'autres solutions que j'ai trouvées, et 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é le découvrir) dans l'espoir qu'il aide les autres.

* note, cette approche est vaguement basée sur GoogleFirebaseObservables. Malheureusement, je manque d'expérience/de temps pour répliquer ce qu'ils ont fait sous le capot. Mais ce qui suit est une façon simpliste de fournir un accès asynchrone à certaines données cache-able.

Situation : un élément "liste de produits" est chargé d'afficher une liste de produits. Le site est une application web d'une page avec quelques boutons de menu qui filtreront les produits affichés sur la page.

Solution : le composant "souscrit" à une méthode de service. La méthode de service renvoie un tableau d'objets produit, auquel le composant accède par le biais du rappel d'abonnement. La méthode de service enveloppe son activité dans un observateur nouvellement créé et renvoie l'observateur. A l'intérieur de cet observateur, il recherche la cache les données sont transmises à l'abonné (le composant) et retournées. Sinon, il émet un appel http pour récupérer les données, s'abonne à la réponse, où vous pouvez traiter ces données (par exemple, associer les données à votre propre modèle) et ensuite transmettre les données à l'abonné.

Le Code

liste de produits.composant.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;
    });
  }
}

produit.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$;
  }
}

du produit.ts (le modèle)

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

voici un exemple de la sortie que je vois lorsque je charge la page dans Chrome. Notez que lors de la charge initiale, les produits sont récupérés à partir de http (appel à mon service de repos de noeud, qui tourne localement sur le port 3000). Quand je clique ensuite pour naviguer vers une vue "filtrée" des produits, les produits se trouvent dans le 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

...[cliquez sur le bouton 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. Dans mon application angulaire, chaque fois que je navigue vers une vue différente des produits, le composant de la liste de produits se recharge. ProductService semble être une instance partagée, de sorte que le cache local de "Produits: Produit []" dans le service ProductService est conservé pendant la navigation, et les appels à "GetProducts ()" renvoie la valeur en cache. Une dernière remarque, j'ai lu des commentaires sur la façon dont les observables/abonnements doivent être fermés lorsque vous avez terminé pour prévenir les "fuites de mémoire". Je n'ai pas inclus ici, mais c'est quelque chose à garder à l'esprit.

3
répondu ObjectiveTC 2017-09-02 08:04:02

je suppose que @ngx-cache/core pourrait être utile pour maintenir les fonctionnalités de mise en cache pour les appels http, surtout si L'appel HTTP est fait à la fois sur les plateformes browser et server .

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 retournée de la méthode faisant l'appel HTTP au cache storage ( le storage peut être configurable, s'il vous plaît vérifier l'implémentation au ng-seed/universal ) - à droite sur la première exécution. La prochaine fois que la méthode est invoquée (peu importe sur navigateur ou serveur plate-forme), la valeur est extraite de la 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 ya aussi la possibilité de utiliser des méthodes de mise en cache( has , get , set ) utilisant la cache 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';
  }
}

Voici la liste des paquets, à la fois pour la mise en cache côté client et Côté Serveur:

2
répondu Burak Tasci 2017-05-03 07:03:12

rxjs 5.3.0

Je n'ai pas été heureux avec .map(myFunction).publishReplay(1).refCount()

avec plusieurs abonnés, .map() exécute myFunction deux fois dans certains cas (je m'attends à ce qu'il n'exécute qu'une seule fois). Une solution semble être publishReplay(1).refCount().take(1)

une autre chose que vous pouvez faire, c'est de ne pas utiliser refCount() et de faire l'Observable chaud tout de suite:

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

cela lancera la requête HTTP quel que soit le nombre d'abonnés. Je ne suis pas sûr que se désabonner avant la fin du HTTP va l'annuler ou pas.

1
répondu Arlo 2017-04-22 00:26:49

ce que nous voulons faire, c'est nous assurer que cela ne provoque pas de multiples requêtes réseau.

mon préféré est d'utiliser les méthodes async pour les appels qui font des requêtes réseau. Les méthodes elles-mêmes ne renvoient pas de valeur, mais mettent à jour un BehaviorSubject dans le même service, auquel les composants s'abonneront.

Pourquoi utiliser un BehaviorSubject au lieu d'un Observable ? Parce,

  • lors de l'abonnement BehaviorSubject renvoie la dernière valeur alors qu'un observable régulier ne déclenche que lorsqu'il reçoit 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:

"1519300920 de la clientèle".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);
}

alors, où que vous le vouliez, nous pouvons simplement vous abonner à customers$ .

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

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

<li *ngFor="let customer of customerService.customers$ | async"> ... </li>

donc maintenant, jusqu'à ce que vous fassiez un autre appel à getCustomers , les données sont conservées dans le customers$ BehaviorSubject.

alors que faire si vous voulez rafraîchir ces données? il suffit de faire un appel à getCustomers()

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

en utilisant cette méthode, nous n'avons pas à conserver explicitement les données entre les appels réseau suivants car elles sont traitées par le BehaviorSubject .

PS: généralement quand un composant est détruit c'est une bonne pratique de se débarrasser des abonnements, pour que vous pouvez utiliser la méthode suggérée dans ce réponse.

1
répondu cyberpirate92 2018-03-24 01:36:31

il suffit d'appeler share () après map et avant toute subscribe .

dans mon cas, j'ai un service générique (RestClientService.ts) qui fait le reste de l'appel, extrait des données, vérifie les erreurs et renvoie observable à un service de mise en œuvre concrète (F. ex.: ContractClientService.ts), enfin cette implémentation concrète renvoie observable à de ContractComponent.ts, et celui-ci abonnez-vous pour mettre à jour la vue.

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

}

Component Contract.ts:

export class ContractComponent implements OnInit {

  getAllItems() {
    this.rcService.getAllItems().subscribe((data) => {
      this.items = data;
   });
  }

}
0
répondu surfealokesea 2016-05-23 09:43:45

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

}

tout est statique à cause de la façon dont nous l'utilisons, mais n'hésitez pas à en faire une classe normale et un service. Je ne suis pas sûr si angular conserve une seule instance pendant tout le temps cependant (nouveau à Angular2).

Et voici comment je l'utilise:

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

je suppose qu'il pourrait y avoir une façon plus intelligente, qui utiliserait quelques trucs Observable mais c'était très bien pour mes buts.

0
répondu Ondra Žižka 2016-12-16 14:40:34

il suffit d'utiliser cette couche cache, elle fait tout ce dont vous avez besoin, et gère même le cache pour les requêtes ajax.

http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html

C'est beaucoup plus facile à utiliser

@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"}]};
}

la couche (en tant que service angulaire injectable) est

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]);
                     }
                     }
                 );
             }
            });
        }
}
0
répondu Ravinder Payal 2017-01-23 10:52:23

C'est .publishReplay(1).refCount(); ou .publishLast().refCount(); depuis observables Http angulaires complet après demande.

cette classe simple cache le résultat afin que vous puissiez vous abonner .valeur plusieurs fois et ne fait qu'une seule demande. Vous pouvez également utiliser .reload() pour faire une nouvelle demande et de publier les données.

Vous pouvez l'utiliser comme:

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

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

}
0
répondu Matjaz Hirsman 2017-11-14 22:40:24

vous pouvez construire une classe simple Cachable<> qui aide à gérer les données récupérées à partir du serveur http avec plusieurs abonnés:

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

}

Utilisation

Déclarer pouvant être mis en Cache<> object (probablement dans le cadre du service):

list: Cacheable<string> = new Cacheable<string>();

et handler:

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

appel d'un composant:

//gets data from server
List.getData().subscribe(…)

Vous pouvez avoir plusieurs composants souscrit.

plus de détails et exemple de code sont ici: http://devinstance.net/articles/20171021/rxjs-cacheable

0
répondu yfranz 2017-11-27 04:26:54

de Grandes réponses.

ou vous pourriez faire ceci:

c'est de la dernière version de rxjs. J'utilise 5.5.7 version de RxJS " 151970920

import {share} from "rxjs/operators";

this.http.get('/someUrl').pipe(share());
0
répondu Jay Modi 2018-06-25 08:52:40

Avez-vous essayé d'exécuter le code que vous avez déjà?

parce que vous construisez L'Observable à partir de la promesse résultant de getJSON() , la demande de réseau est faite avant que quiconque s'abonne. Et la promesse est partagée par tous les abonnés.

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
// ...
-2
répondu Brandon 2016-03-29 13:48:17