Al usar Http, llamamos a un método que realiza una llamada de red y devuelve un http observable:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
Si tomamos esto observable y le agregamos múltiples suscriptores:
let network$ = getCustomer();
let subscriber1 = network$.subscribe(...);
let subscriber2 = network$.subscribe(...);
Lo que queremos hacer es asegurarnos de que esto no provoque múltiples solicitudes de red.
Esto puede parecer un escenario inusual, pero en realidad es bastante común: por ejemplo, si la persona que llama se suscribe a lo observable para mostrar un mensaje de error y lo pasa a la plantilla utilizando la tubería asíncrona, ya tenemos dos suscriptores.
¿Cuál es la forma correcta de hacerlo en RxJs 5?
A saber, esto parece funcionar bien:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json()).share();
}
Pero, ¿es esta la forma idiomática de hacer esto en RxJs 5, o deberíamos hacer algo más?
Nota: Según Angular 5 nuevo HttpClient
, la .map(res => res.json())
parte en todos los ejemplos ahora es inútil, ya que el resultado JSON ahora se supone por defecto.
fuente
Respuestas:
Caché los datos y si están disponibles en caché, devuélvalos, de lo contrario realice la solicitud HTTP.
Ejemplo de Plunker
Este artículo https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html es una gran explicación sobre cómo almacenar en caché
shareReplay
.fuente
do()
al contrario demap()
no modifica el evento. También podría usarlomap()
, pero luego tuvo que asegurarse de que se devuelva el valor correcto al final de la devolución de llamada..subscribe()
no necesita el valor, puede hacerlo porque podría obtener solonull
(dependiendo de lo quethis.extractData
regrese), pero en mi humilde opinión, esto no expresa bien la intención del código.this.extraData
termina deextraData() { if(foo) { doSomething();}}
otra manera, se devuelve el resultado de la última expresión, que podría no ser lo que desea.if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
Según la sugerencia de @Cristian, esta es una forma que funciona bien para los observables HTTP, que solo emiten una vez y luego completan:
fuente
share
operador podría ser una opción razonable (aunque con algunos casos extremos desagradables). Para una discusión profunda sobre las opciones, vea la sección de comentarios en esta publicación de blog: blog.jhades.org/…publishLast().refCount()
no puede cancelarse, una vez que todas las suscripciones a la observable devuelta porrefCount
han sido canceladas, el efecto neto es que la fuente observable será anulada, cancelando si está "en vuelo"ACTUALIZACIÓN: Ben Lesh dice que la próxima versión menor después de 5.2.0, podrá llamar a shareReplay () para realmente almacenar en caché.
PREVIAMENTE.....
En primer lugar, no use share () o publishingReplay (1) .refCount (), son lo mismo y el problema es que solo se comparte si se realizan conexiones mientras el observable está activo, si se conecta después de que se complete , crea un nuevo observable de nuevo, traducción, no realmente almacenamiento en caché.
Birowski dio la solución correcta arriba, que es usar ReplaySubject. ReplaySubject almacenará en caché los valores que le dé (bufferSize) en nuestro caso 1. No creará un nuevo observable como share () una vez que refCount llegue a cero y realice una nueva conexión, que es el comportamiento correcto para el almacenamiento en caché.
Aquí hay una función reutilizable
Aquí se explica cómo usarlo
A continuación se muestra una versión más avanzada de la función almacenable en caché. Esta permite tener su propia tabla de búsqueda + la capacidad de proporcionar una tabla de búsqueda personalizada. De esta manera, no tiene que verificar esto._caché como en el ejemplo anterior. Observe también que, en lugar de pasar el observable como primer argumento, pasa una función que devuelve los observables, esto se debe a que Http de Angular se ejecuta de inmediato, por lo que al devolver una función ejecutada de manera diferida, podemos decidir no llamarla si ya está en nuestro caché
Uso:
fuente
const data$ = this._http.get('url').pipe(cacheable()); /*1st subscribe*/ data$.subscribe(); /*2nd subscribe*/ data$.subscribe();
:? Por lo tanto, se comporta más como cualquier otro operador ..rxjs 5.4.0 tiene un nuevo método shareReplay .
El autor dice explícitamente "ideal para manejar cosas como el almacenamiento en caché de resultados AJAX"
rxjs PR # 2443 feat (shareReplay): agrega una
shareReplay
variante depublishReplay
fuente
de acuerdo con este artículo
así que adentro si las declaraciones solo se agregan
a
.map(...)
fuente
rxjs versión 5.4.0 (2017-05-09) agrega soporte para shareReplay .
Puede modificar fácilmente un servicio angular para usar esto y devolver un observable con un resultado en caché que solo realizará la llamada http una sola vez (suponiendo que la primera llamada haya sido exitosa).
Ejemplo de servicio angular
Aquí hay un servicio al cliente muy simple que utiliza
shareReplay
.customer.service.ts
Tenga en cuenta que la asignación en el constructor podría moverse al método,
getCustomers
pero como los observables devueltosHttpClient
son "fríos", hacer esto en el constructor es aceptable ya que la llamada http solo se realizará con la primera llamada asubscribe
.También se supone aquí que los datos devueltos iniciales no se vuelven obsoletos durante la vida útil de la instancia de la aplicación.
fuente
Destaqué la pregunta, pero intentaré intentarlo.
Aquí está la prueba :)
Solo hay una comida para llevar:
getCustomer().subscribe(customer$)
No nos estamos suscribiendo a la respuesta api de
getCustomer()
, nos estamos suscribiendo a un ReplaySubject que es observable y que también puede suscribirse a un Observable diferente y (y esto es importante) mantener su último valor emitido y volver a publicarlo en cualquiera de sus (ReplaySubject's ) suscriptores.fuente
Encontré una manera de almacenar el resultado de http en sessionStorage y usarlo para la sesión, para que nunca vuelva a llamar al servidor.
Lo usé para llamar a github API para evitar el límite de uso.
Para su información, el límite de almacenamiento de sesión es de 5M (o 4.75M). Por lo tanto, no debe usarse así para un gran conjunto de datos.
------ editar -------------
Si desea tener datos actualizados con F5, que utiliza datos de memoria en lugar de sessionStorage;
fuente
sessionStorage
indica, lo usaría solo para datos que se espera sean consistentes durante toda la sesión.getCustomer
en su ejemplo. ;) Así que solo quería advertir a algunas personas que podrían no ver los riesgos :)La implementación que elija dependerá de si desea cancelar la suscripción () para cancelar su solicitud HTTP o no.
En cualquier caso, los decoradores TypeScript son una buena manera de estandarizar el comportamiento. Este es el que escribí:
Definición del decorador:
fuente
Property 'connect' does not exist on type '{}'.
de la lineareturnValue.connect();
. ¿Puedes elaborar?Datos de respuesta HTTP en caché utilizando Rxjs Observer / Observable + Caching + Subscription
Ver código abajo
* descargo de responsabilidad: soy nuevo en rxjs, así que tenga en cuenta que podría estar haciendo un mal uso del enfoque observable / observador. Mi solución es puramente un conglomerado de otras soluciones que encontré, y es la consecuencia de no haber encontrado una solución simple y bien documentada. Por lo tanto, estoy proporcionando mi solución de código completa (como me hubiera gustado encontrar) con la esperanza de que ayude a otros.
* nota, este enfoque se basa libremente en GoogleFirebaseObservables. Desafortunadamente, me falta la experiencia / tiempo adecuado para replicar lo que hicieron bajo el capó. Pero la siguiente es una forma simplista de proporcionar acceso asíncrono a algunos datos aptos para caché.
Situación : el componente 'lista de productos' tiene la tarea de mostrar una lista de productos. El sitio es una aplicación web de una sola página con algunos botones de menú que 'filtrarán' los productos que se muestran en la página.
Solución : El componente "se suscribe" a un método de servicio. El método de servicio devuelve una matriz de objetos de producto, a los que accede el componente a través de la devolución de llamada de suscripción. El método de servicio envuelve su actividad en un observador recién creado y devuelve el observador. Dentro de este observador, busca datos almacenados en caché y los devuelve al suscriptor (el componente) y los devuelve. De lo contrario, emite una llamada http para recuperar los datos, se suscribe a la respuesta, donde puede procesar esos datos (por ejemplo, asignar los datos a su propio modelo) y luego pasar los datos al suscriptor.
El código
product-list.component.ts
product.service.ts
product.ts (el modelo)
Aquí hay una muestra de la salida que veo cuando cargo la página en Chrome. Tenga en cuenta que en la carga inicial, los productos se obtienen de http (llamar al servicio de reposo de mi nodo, que se ejecuta localmente en el puerto 3000). Cuando hago clic para navegar a una vista 'filtrada' de los productos, los productos se encuentran en caché.
Mi registro de Chrome (consola):
... [hizo clic en un botón de menú para filtrar los productos] ...
Conclusión: esta es la forma más simple que he encontrado (hasta ahora) para implementar datos de respuesta http almacenables en caché. En mi aplicación angular, cada vez que navego a una vista diferente de los productos, el componente de la lista de productos se recarga. ProductService parece ser una instancia compartida, por lo que la memoria caché local de 'productos: Producto []' en ProductService se retiene durante la navegación, y las llamadas posteriores a "GetProducts ()" devuelve el valor almacenado en caché. Una nota final, he leído comentarios sobre cómo deben cerrarse los observables / suscripciones cuando haya terminado para evitar 'pérdidas de memoria'. No he incluido esto aquí, pero es algo a tener en cuenta.
fuente
Supongo que @ ngx-cache / core podría ser útil para mantener las características de almacenamiento en caché para las llamadas http, especialmente si la llamada HTTP se realiza tanto en las plataformas del navegador como en las del servidor .
Digamos que tenemos el siguiente método:
Se puede utilizar el
Cached
decorador de @ NGX-cache / núcleo para almacenar el valor devuelto por el método de hacer la llamada HTTP en elcache storage
( lastorage
puede ser configurable, por favor verifica la aplicación a ng-semilla / universal ) - a la derecha en la primera ejecución. Las próximas veces que se invoca el método (no importa en el navegador o en la plataforma del servidor ), el valor se recupera decache storage
.También existe la posibilidad de usar métodos de almacenamiento en caché (
has
,get
,set
) utilizando el API de almacenamiento en caché .anyclass.ts
Aquí está la lista de paquetes, tanto para el almacenamiento en caché del lado del cliente como del lado del servidor:
fuente
rxjs 5.3.0
No he sido feliz con
.map(myFunction).publishReplay(1).refCount()
Con varios suscriptores, se
.map()
ejecutamyFunction
dos veces en algunos casos (espero que solo se ejecute una vez). Una solución parece serpublishReplay(1).refCount().take(1)
Otra cosa que puede hacer es simplemente no usar
refCount()
y hacer que el Observable se caliente inmediatamente:Esto iniciará la solicitud HTTP independientemente de los suscriptores. No estoy seguro de si cancelar la suscripción antes de que finalice HTTP GET lo cancele o no.
fuente
Mi favorito personal es utilizar
async
métodos para llamadas que hacen solicitudes de red. Los métodos en sí mismos no devuelven un valor, sino que actualizan unoBehaviorSubject
dentro del mismo servicio, al que se suscribirán los componentes.Ahora, ¿por qué usar un en
BehaviorSubject
lugar de unObservable
? Porque,onnext
.getValue()
método.Ejemplo:
customer.service.ts
Luego, cuando sea necesario, podemos suscribirnos
customers$
.O tal vez quieras usarlo directamente en una plantilla
Así que ahora, hasta que realice otra llamada
getCustomers
, los datos se retienen encustomers$
BehaviorSubject.Entonces, ¿qué pasa si desea actualizar estos datos? solo llama a
getCustomers()
Con este método, no tenemos que retener explícitamente los datos entre las llamadas de red posteriores, ya que es manejado por
BehaviorSubject
.PD: Por lo general, cuando un componente se destruye, es una buena práctica deshacerse de las suscripciones, para eso puede usar el método sugerido en esta respuesta.
fuente
Grandes respuestas
O podrías hacer esto:
Esto es de la última versión de rxjs. Estoy usando la versión 5.5.7 de RxJS
fuente
Simplemente llame a share () después del mapa y antes de suscribirse .
En mi caso, tengo un servicio genérico (RestClientService.ts) que realiza la llamada de descanso, extrae datos, verifica si hay errores y regresa observable a un servicio de implementación concreto (p. Ej .: ContractClientService.ts), finalmente esta implementación concreta devuelve observable a de ContractComponent.ts, y este se suscribe para actualizar la vista.
RestClientService.ts:
ContractService.ts:
ContractComponent.ts:
fuente
Escribí una clase de caché
Todo es estático debido a cómo lo usamos, pero siéntase libre de convertirlo en una clase normal y un servicio. Sin embargo, no estoy seguro de si angular mantiene una sola instancia durante todo el tiempo (nuevo en Angular2).
Y así es como lo uso:
Supongo que podría haber una forma más inteligente, que usaría algunos
Observable
trucos, pero esto estaba bien para mis propósitos.fuente
Simplemente use esta capa de caché, hace todo lo que necesita e incluso administra la caché para las solicitudes de ajax.
http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html
Es muy fácil de usar.
La capa (como un servicio angular inyectable) es
fuente
Es
.publishReplay(1).refCount();
o.publishLast().refCount();
desde que los observables de Angular Http se completan después de la solicitud.Esta clase simple almacena en caché el resultado para que pueda suscribirse a .value muchas veces y solo realiza 1 solicitud. También puede usar .reload () para hacer una nueva solicitud y publicar datos.
Puedes usarlo como:
y la fuente:
fuente
Puede crear una clase simple Cacheable <> que ayuda a administrar los datos recuperados del servidor http con múltiples suscriptores:
Uso
Declarar el objeto Cacheable <> (presumiblemente como parte del servicio):
y manejador:
Llamar desde un componente:
Puede tener varios componentes suscritos a él.
Más detalles y ejemplos de código están aquí: http://devinstance.net/articles/20171021/rxjs-cacheable
fuente
¡Simplemente podría usar ngx-cacheable ! Se adapta mejor a tu escenario.
Entonces, su clase de servicio sería algo como esto:
Aquí está el enlace para más referencia.
fuente
¿Has intentado ejecutar el código que ya tienes?
Debido a que está construyendo el Observable a partir de la promesa resultante
getJSON()
, la solicitud de red se realiza antes de que nadie se suscriba. Y la promesa resultante es compartida por todos los suscriptores.fuente