EDIT 2: lo que funcionó aún mejor fue shareReplay(1) , como se indica en la actualización de la respuesta de @karser .
EDIT 1: Lo que terminó funcionando mejor fue esto:
@Injectable() export class MyGlobalService { private resource$; private resource$Connected; constructor(private http: Http) { this.resource$Connected = false; this.resource$ = this.http .get('/api/resource') .map((res: Response) => res.json()) .publishReplay(1); } getResource(): Observable<any> { if (!this.resource$Connected) { this.resource$.connect(); this.resource$Connected = true; } return this.resource$; } }Solo realiza la llamada AJAX una vez y no llama hasta que algún consumidor requiera el recurso.
PREGUNTA ORIGINAL:
Estoy tratando de almacenar en caché una llamada HTTP angular y multidifundir el resultado más reciente a todos los suscriptores actuales y futuros. Los resultados de ajax no cambiarán durante la vida útil de la aplicación, por lo que no quiero hacer llamadas adicionales para un recurso que ya tengo. Por lo tanto, quiero que continúe "conectado" incluso cuando todos los suscriptores se den de baja. es posible?
Lo que probé inicialmente fue esto:
// in a global service getResource(): Observable<any> { return this.http .get('/api/resource') .map((res: Response) => res.json()) .publishLast() .refCount(); } Esto funciona bien para varias canalizaciones async en el mismo componente, pero si ese componente se destruye (y, por lo tanto, refCount pasa a 0), la solicitud HTTP se repetirá en una instancia posterior del componente.
Para combatir esto, comencé a almacenar manualmente en caché los resultados:
resourceResults: any; getResource(): Observable<any> { if (resourceResults) { return Observable.of(this.resourceResults); } return this.http .get('/api/resource') .map((res: Response) => res.json()) .do(x => this.resourceResults = x) .publishLast() .refCount(); }Esto funciona bien, pero siento que hay una forma más rx de hacerlo.
Intenté usar connect() , pero parece tener el mismo problema que mi primer ejemplo. Una vez que todos los suscriptores se han dado de baja, el uso de connect() hace que la solicitud HTTP vuelva a ocurrir
resource$ = this.http .get('/api/resource') .map((res: Response) => res.json()) .publishLast() .refCount(); getResource(): Observable<any> { this.resource$.connect(); return this.resource$; }¿Algunas ideas?
publicarReproducir/conectar funciona. Aquí está el plunker de trabajo :
import {Injectable} from '@angular/core'; import {Http, Response} from '@angular/http'; import {Observable} from "rxjs/Observable"; @Injectable() export class YourService { resource:Observable<any>; constructor(private http: Http) { this.resource = this.http.get('https://api.github.com/users/karser') .map((res: Response) => res.json()) .do(res => console.log('response', res)) .publishReplay(1); this.resource.connect(); } }La salida:
Subscribing response Object {login: "karser", id: 1675033…} Unscibscribed Subscribing once againACTUALIZACIÓN: RxJS 5.4 tiene el operador shareReplay que aparentemente hace lo mismo. Ver el plunkr actualizado
this.http.get('https://api.github.com/users/karser') .map((res: Response) => res.json()) .shareReplay(1);De la solicitud de extracción:
shareReplaydevuelve un observable que es la fuente multidifundida sobre unReplaySubject. Ese tema de reproducción se recicla en caso de error de la fuente, pero no al completar la fuente. Esto hace queshareReplaysea ideal para manejar cosas como almacenar en caché los resultados de AJAX, ya que se puede volver a intentar. Su comportamiento de repetición, sin embargo, difiere de compartir en que no repetirá la fuente observable, sino que repetirá los valores de la fuente observable.
En primer lugar, si desea compartir esta respuesta durante toda la vida útil de la aplicación, debe colocarla en un servicio y asegurarse de que todos sus componentes compartan la misma instancia.
En teoría, podría hacer que la fuente Observable nunca se complete. Para ser más precisos, podría hacer que la cadena nunca propague la señal completa, pero esto no se recomienda y podría generar un comportamiento inesperado si intentara usar este Observable con operadores como forkJoin() o toArray() y cualquier similar que requiera el fuente Observable para completar correctamente.
En su lugar, puede usar publishReplay(1) para mantener el último valor emitido por el Observable de origen, refCount() para mantener una suscripción única al Observable de origen mientras se consulta el caché y luego take(1) para aceptar solo un valor y completar .
const cache = this.http.get('/api/resource') .map((res: Response) => res.json()) .publishReplay(1) .refCount() .take(1); Por lo tanto, puede suscribirse a varios observadores a la vez y solo se realizará una solicitud HTTP. Luego, cualquier suscripción posterior llegará a .publishReplay(1) que ya tiene un valor en caché que se propaga de inmediato y take(1) completa la cadena de inmediato. Por lo tanto, no habrá suscripción a this.http.get('/api/resource') y, por lo tanto, no se realizará ninguna solicitud.
No utilice refCount. Cree un observable conectable manualmente, conéctese, suscríbase y ReplaySubject del conectable mantendrá el último valor publicado. Algo como eso:
let obs$ = Rx.Observable.interval(5000) .multicast(new Rx.ReplaySubject(1)); obs$.connect(); // unsuscribe when service is destroyed??? let mainSubecription = obs$.subscribe(x=>console.log(x)); // create subscription (like async pipe) let subscription; setTimeout(()=> { subscription = obs$.subscribe(x=>console.log('second', x)); }, 15000); // remove subscription (like async pipe) setTimeout(()=> { subscription.unsubscribe(); }, 40000); // the main subscription still gets data