Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

119
Views
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 answers
Answer question

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 Report

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 Report

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 Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!