Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

120
Vistas
Keep observable connected even with no subscribers

EDIT 2: What worked even better was shareReplay(1), as stated in the update of @karser's answer.


EDIT 1: What ended up working best was this:

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

}

It only makes the AJAX call once, and it doesn't call until some consumer requires the resource.


ORIGINAL QUESTION:

I'm trying to cache an angular HTTP call and multicast the most recent result to all current and future subscribers. The ajax results won't change over the application lifetime, so I don't want to make any extra calls for a resource I already have. Therefore, I want it to continue to stay "connected" even when all the subscribers unsubscribe. Is this possible?

What I initially tried was this:

// in a global service 

getResource(): Observable<any> {
  return this.http
    .get('/api/resource')
    .map((res: Response) => res.json())
    .publishLast()
    .refCount();
}

This works well for multiple async pipes in the same component, but if that component is destroyed (and thus refCount goes to 0) the HTTP request will be repeated on a later instantiation of the component.

To combat this, I started manually caching the results:

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

This works fine, but I feel like there is a more rx way to do it.

I've tried using connect(), but that seems to suffer the same issue as my first example. Once all the subscribers have unsubscribed, using connect() causes the HTTP request to happen again

resource$ = this.http
  .get('/api/resource')
  .map((res: Response) => res.json())
  .publishLast()
  .refCount();

getResource(): Observable<any> {
  this.resource$.connect();
  return this.resource$;
}

Any ideas?

over 4 years ago · Santiago Trujillo
3 Respuestas
Responde la pregunta

0

publishReplay/connect works. Here is the working plunker:

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

The output:

Subscribing
response Object {login: "karser", id: 1675033…}
Unscibscribed
Subscribing once again

UPDATE: RxJS 5.4 has shareReplay operator which apparently does the same thing. See the updated plunkr

this.http.get('https://api.github.com/users/karser')
    .map((res: Response) => res.json())
    .shareReplay(1);

From pull request:

shareReplay returns an observable that is the source multicasted over a ReplaySubject. That replay subject is recycled on error from the source, but not on completion of the source. This makes shareReplay ideal for handling things like caching AJAX results, as it's retryable. It's repeat behavior, however, differs from share in that it will not repeat the source observable, rather it will repeat the source observable's values.

over 4 years ago · Santiago Trujillo Denunciar

0

First of all if you want to share this response during the entire application lifetime you should put it into a service and make sure you all your components are sharing the same instance.

You could theoretically make the source Observable to never complete. To be more precise you could make just the chain to never propage the complete signal but this is not recommended and potentially lead to unexpected behavior if you tried to use this Observable with operators such forkJoin() or toArray() and any similar that require the source Observable to properly complete.

Instead you can use publishReplay(1) to keep the last value emitted by the source Observable, refCount() to hold a single subscription to the source Observable while the cache is being queried and then take(1) to accept only one value and complete.

const cache = this.http.get('/api/resource')
  .map((res: Response) => res.json())
  .publishReplay(1)
  .refCount()
  .take(1);

So you can subscribe multiple observers at once and only one HTTP request will be performed. Then any subsequent subscriptions will hit the .publishReplay(1) that already has a cached value which is immediately propagated and take(1) completes the chain right away. So there'll be no subscription to this.http.get('/api/resource') and therefore no request will be made.

over 4 years ago · Santiago Trujillo Denunciar

0

Do not use refCount. Create connectable observable manually, do connect, subscribe on it and ReplaySubject of the connectable will keep the last published value. Something like that:

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
over 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda