En utilisant Http, nous appelons une méthode qui effectue un appel réseau et renvoie un http observable:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
Si nous prenons cela 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 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 à l'aide du canal asynchrone, nous avons déjà deux abonnés.
Quelle est la bonne façon de procéder 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 le faire dans RxJs 5, ou devrions-nous faire autre chose à la place?
Remarque: Conformément à Angular 5 new HttpClient
, la .map(res => res.json())
pièce dans tous les exemples est désormais inutile, car le résultat JSON est désormais supposé par défaut.
la source
Réponses:
Mettez les données en cache et, si elles sont disponibles en cache, renvoyez-les sinon faites la requête HTTP.
Exemple Plunker
Cet article https://blog.oughttram.io/angular/2018/03/05/advanced-caching-with-rxjs.html est une excellente explication sur la façon de mettre en cache avec
shareReplay
.la source
do()
contrairement àmap()
ne modifie pas l'événement. Vous pouvez également l'utilisermap()
, mais vous devez ensuite vous assurer que la valeur correcte est renvoyée à la fin du rappel..subscribe()
n'a pas besoin de la valeur, vous pouvez le faire car cela pourrait être justenull
(en fonction de ce quithis.extractData
retourne), mais à mon humble avis, cela n'exprime pas bien l'intention du code.this.extraData
se termine commeextraData() { if(foo) { doSomething();}}
autrement, le résultat de la dernière expression est renvoyé, ce qui pourrait ne pas être ce que vous voulez.if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
Selon la suggestion de @Cristian, c'est une façon qui fonctionne bien pour les observables HTTP, qui n'émettent qu'une seule fois puis se terminent:
la source
share
opérateur peut être un choix raisonnable (bien qu'avec certains cas de bord méchants). Pour une discussion approfondie sur les options, voir la section des commentaires dans cet article de blog: blog.jhades.org/…publishLast().refCount()
ne puisse pas être annulée, une fois que tous les abonnements à l'observable retourné parrefCount
ont été annulés, l'effet net est que la source observable sera désinscrite, l'annulant si elle était "en vol"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.....
Tout d'abord, n'utilisez pas share () ou publishReplay (1) .refCount (), ils sont les mêmes et le problème avec cela, c'est qu'il ne partage que si les connexions sont établies alors que l'observable est actif, si vous vous connectez après qu'il se 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 consiste à utiliser ReplaySubject. ReplaySubject mettra en cache les valeurs que vous lui donnez (bufferSize) dans notre cas 1. Il ne créera pas un nouveau observable comme share () une fois que refCount atteint zéro et que vous établissez une nouvelle connexion, ce qui est le bon comportement pour la mise en cache.
Voici une fonction réutilisable
Voici comment l'utiliser
Vous trouverez ci-dessous une version plus avancée de la fonction pouvant être mise en cache. Celle-ci permet d'avoir sa propre table de recherche + 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 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.
Usage:
la source
const data$ = this._http.get('url').pipe(cacheable()); /*1st subscribe*/ data$.subscribe(); /*2nd subscribe*/ data$.subscribe();
? Il se comporte donc plus comme n'importe quel autre opérateur ..rxjs 5.4.0 a une nouvelle méthode shareReplay .
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 une
shareReplay
variante depublishReplay
la source
selon cet article
donc à l' intérieur si les déclarations ne font qu'ajouter
à
.map(...)
la source
rxjs version 5.4.0 (2017-05-09) ajoute la prise en charge de shareReplay .
Vous pouvez facilement modifier un service angulaire pour l'utiliser et renvoyer un observable avec un résultat mis en cache qui ne fera que l'appel http une seule fois (en supposant que le premier appel a réussi).
Exemple de service angulaire
Voici un service client très simple qui utilise
shareReplay
.customer.service.ts
Notez que l'affectation dans le constructeur peut être déplacée vers la méthode,
getCustomers
mais comme les observables retournésHttpClient
sont "à froid", le faire dans le constructeur est acceptable car l'appel http ne sera effectué que lors du premier appel àsubscribe
.L'hypothèse ici est également que les données retournées initiales ne sont pas périmées pendant la durée de vie de l'instance d'application.
la source
J'ai mis la question en vedette, mais je vais essayer de l'essayer.
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 s'abonner à un autre Observable et (et c'est important) de conserver sa dernière valeur émise et de la republier à l'un de ses (ReplaySubject's ) les abonnés.la source
J'ai trouvé un moyen de stocker le résultat http get 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 pour éviter la limite d'utilisation.
Pour info, la limite de sessionStorage est de 5M (ou 4.75M). Il ne doit donc pas être utilisé comme ceci pour un grand ensemble de données.
------ edit -------------
Si vous voulez avoir des données actualisées avec F5, qui utilise des données de mémoire au lieu de sessionStorage;
la source
sessionStorage
indique, je ne l'utiliserais que pour des données qui devraient être cohérentes pour toute la session.getCustomer
dans son exemple. ;) Alors je voulais juste avertir certains ppl qui pourraient ne pas voir les risques :)L'implémentation que vous choisirez dépendra si vous souhaitez que unsubscribe () annule ou non votre requête HTTP.
Dans tous les cas, les décorateurs TypeScript sont un bon moyen de normaliser le comportement. Voici celui que j'ai écrit:
Définition du décorateur:
la source
Property 'connect' does not exist on type '{}'.
de la lignereturnValue.connect();
. Peux-tu élaborer?Données de réponse HTTP pouvant être mises en cache à 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 mal utiliser l'approche observable / observateur. 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 de code complète (comme j'aurais aimé la trouver) dans l'espoir qu'elle aide les autres.
* notez que cette approche est vaguement basée sur GoogleFirebaseObservables. Malheureusement, je manque d'expérience / de temps pour reproduire ce qu'ils ont fait sous le capot. Mais ce qui suit est un moyen simpliste de fournir un accès 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 seule page avec quelques 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'objets 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 retransmet à l'abonné (le composant) et retourne. 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, mapper les données à votre propre modèle), puis transmettre les données à l'abonné.
Le code
product-list.component.ts
product.service.ts
product.ts (le modèle)
Voici un exemple de la sortie que je vois lorsque je charge la page dans Chrome. Notez qu'à la charge initiale, les produits sont récupérés depuis http (appel à mon service de repos de noeud, qui s'exécute localement sur le port 3000). Lorsque je clique ensuite pour accéder à une vue «filtrée» des produits, les produits sont trouvés dans le cache.
Mon journal Chrome (console):
... [cliqué sur un bouton de menu pour filtrer les produits] ...
Conclusion: C'est la manière la plus simple que j'ai trouvée (jusqu'à présent) pour implémenter des données de réponse http pouvant être mises en cache. 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, donc le cache local de «products: Product []» dans ProductService est conservé pendant la navigation, et les appels ultérieurs à «GetProducts ()» renvoient 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 «fuites de mémoire». Je ne l'ai pas inclus ici, mais c'est quelque chose à garder à l'esprit.
la source
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:
Vous pouvez utiliser le
Cached
décorateur de @ ngx-cache / core pour stocker la valeur renvoyée par la méthode faisant l'appel HTTP sur lecache storage
( lestorage
peut être configurable, veuillez vérifier l'implémentation sur ng-seed / universal ) - dès la première exécution. La prochaine fois que la méthode est invoquée (quel que soit le navigateur ou la plate-forme de serveur ), la valeur est récupérée à partir ducache storage
.Il y a aussi la possibilité de méthodes de mise en cache d'utilisation (
has
,get
,set
) en utilisant l' API de mise en cache .anyclass.ts
Voici la liste des packages, pour la mise en cache côté client et côté serveur:
la source
rxjs 5.3.0
Je ne suis pas content
.map(myFunction).publishReplay(1).refCount()
Avec plusieurs abonnés,
.map()
exécutemyFunction
deux fois dans certains cas (je m'attends à ce qu'il ne s'exécute qu'une seule fois). Une solution semble êtrepublishReplay(1).refCount().take(1)
Une autre chose que vous pouvez faire est simplement de ne pas utiliser
refCount()
et de rendre l'Observable chaud tout de suite:Cela lancera la requête HTTP indépendamment des abonnés. Je ne sais pas si la désinscription avant la fin de HTTP GET l'annulera ou non.
la source
Mon préféré est d'utiliser des
async
méthodes pour les appels qui font des requêtes réseau. Les méthodes elles-mêmes ne renvoient pas de valeur, mais elles mettent à jour unBehaviorSubject
dans le même service, auquel les composants s'abonneront.Maintenant, pourquoi utiliser un
BehaviorSubject
au lieu d'unObservable
? Car,onnext
.getValue()
méthode.Exemple:
customer.service.ts
Ensuite, lorsque cela est nécessaire, nous pouvons simplement nous abonner
customers$
.Ou peut-être souhaitez-vous l'utiliser directement dans un modèle
Alors maintenant, jusqu'à ce que vous fassiez un autre appel à
getCustomers
, les données sont conservées dans lecustomers$
BehaviorSubject.Et si vous souhaitez actualiser ces données? il suffit d'appeler
getCustomers()
En utilisant cette méthode, nous n'avons pas à 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 se débarrasser des abonnements, pour cela, vous pouvez utiliser la méthode suggérée dans cette réponse.
la source
Excellentes réponses.
Ou vous pouvez le faire:
Il s'agit de la dernière version de rxjs. J'utilise la version 5.5.7 de RxJS
la source
Appelez simplement share () après la carte et avant de vous abonner .
Dans mon cas, j'ai un service générique (RestClientService.ts) qui fait le reste, extrait les données, vérifie les erreurs et renvoie observable à un service d'implémentation concret (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:
ContractService.ts:
ContractComponent.ts:
la source
J'ai écrit une classe de cache,
Tout est statique en raison de la façon dont nous l'utilisons, mais n'hésitez pas à en faire une classe et un service normaux. Je ne sais pas si angular conserve une seule instance pendant tout le temps (nouveau pour Angular2).
Et voici comment je l'utilise:
Je suppose qu'il pourrait y avoir un moyen plus intelligent, qui utiliserait quelques
Observable
astuces, mais c'était très bien pour mes besoins.la source
Utilisez simplement cette couche de 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 tellement simple à utiliser
La couche (en tant que service angulaire injectable) est
la source
C'est
.publishReplay(1).refCount();
ou.publishLast().refCount();
puisque les observables Angular Http se terminent après la demande.Cette classe simple met en cache le résultat afin que vous puissiez vous abonner à .value plusieurs fois et ne faire qu'une seule demande. Vous pouvez également utiliser .reload () pour effectuer une nouvelle demande et publier des données.
Vous pouvez l'utiliser comme:
et la source:
la source
Vous pouvez créer une classe simple Cacheable <> qui aide à gérer les données récupérées du serveur http avec plusieurs abonnés:
Usage
Déclarez l'objet Cacheable <> (probablement dans le cadre du service):
et gestionnaire:
Appel depuis un composant:
Vous pouvez y souscrire plusieurs composants.
Plus de détails et un exemple de code sont ici: http://devinstance.net/articles/20171021/rxjs-cacheable
la source
Vous pouvez simplement utiliser ngx-cacheable ! Cela convient mieux à votre scénario.
Donc, votre classe de service serait quelque chose comme ça -
Voici le lien pour plus de référence.
la source
Avez-vous essayé d'exécuter le code que vous avez déjà?
Parce que vous construisez l'Observable à partir de la promesse qui en résulte
getJSON()
, la demande réseau est effectuée avant que quiconque ne s'abonne. Et la promesse qui en résulte est partagée par tous les abonnés.la source