14

I have created a service that makes a simple GET request:

private accountObservable = null;

constructor(private _http: Http) {
}

getAccount () {
    // If we have account cached, use it instead
    if (this.accountObservable === null) {
        this.accountObservable = this._http.get('http://localhost/api/account')
            .map(res => <Account> res.json().data)
            .catch(this.handleError);
    }

    return this.accountObservable;
}

I have added that service in my bootstrap function to provide it globally (my hope is to provide the same instance to all components):

provide(AccountService, { useClass: AccountService })

The problem is when I call this service in different components, a GET request is made every time. So if I add it to 3 components, 3 GET requests will be made even though I check if an observable already exist.

ngOnInit() {
  this._accountService.getAccount().subscribe(
    account => this.account = account,
    error =>  this.errorMessage = <any>error
  );
}

How can I prevent the GET request to be made multiple times?

Christoffer
  • 514
  • 1
  • 5
  • 17
  • 1
    Are you only `provide`ing the service at the bootstrap level? (It sounds like you might be listing it in the `providers` array in all of your components, which would create 3 instances of your service). If you only provide it at the bootstrap level, try using [Observable.share()](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/share.md). (These are all just guesses on my part.) – Mark Rajcok Feb 28 '16 at 00:53
  • 1
    See also http://stackoverflow.com/a/35527616/215945 for a slightly different implementation of caching a server result. – Mark Rajcok Feb 28 '16 at 01:01
  • 1
    Yes, I only do `provide` on bootstrap. I have tried adding console.log() in construct of the service and it's only fired once. I added `share()` after `get()` but it made no difference. The other post didn't work either. I guess it's because until we have received data, a new request will be made. So if 3 components are running getAccount() at the same time all will make requests and it won't stop until one of them have got response. – Christoffer Feb 28 '16 at 06:36

4 Answers4

21

Use Observable.share():

if (this.accountObservable === null) {
    this.accountObservable = this._http.get('./data/data.json')
      .share()
      .map(res => res.json())
      .catch(this.handleError);
}

Plunker

In the Plunker, AppComponent and Component2 both call getAccount().subscribe() twice.

With share(), the Chrome Developer tools Network tab shows one HTTP request for data.json. With share() commented out, there are 4 requests.

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • 9
    This doesn't work. Saw this problem a while back, and this solution, seeing it again, solution still doesn't work. – Rick O'Shea Sep 30 '16 at 21:50
  • Also don't forget to add `import 'rxjs/add/operator/share';` @RickO'Shea This answer works perfectly well. If it doesn't work for you then you might want to post a plunkr that demonstrates your problem with it. – jlh Aug 16 '17 at 08:23
  • 2
    on newer rxjs you need to use `pipe` so need to update to: `.get(...).pipe(share(), map(...))`. source: https://www.learnrxjs.io/operators/multicasting/share.html – jurl Apr 10 '19 at 14:50
  • Here's an updated answer @RickO'Shea and others still trying to figure this out: https://stackoverflow.com/a/60081534/4843137 – Kyle Krzeski Feb 05 '20 at 17:58
  • I added share method inside pipe I worked code: .get(...).pipe(share(), – Kumaresan Perumal May 11 '20 at 07:11
2

There are two types of observables.

Cold Observable : each subscriber receive all the events ( from the begining )

Hot observable : each subscriber receive the events that are emited after subscription.

Cold Observables are the default one. That's what the WS calling is triggered many times.

To make an Observable Hot you have to use following Rx's operators chain :

.publish().refCount()

In your case :

getAccount () {

    let accountObservable = this._http.get('http://localhost/api/account')
            .map(res => <Account> res.json().data)
            .catch(this.handleError);

    return accountObservable.publish().refCount();
}
Mourad Zouabi
  • 2,187
  • 2
  • 15
  • 20
0

In my case it was because of form post and button clik was set to same listener

cahit beyaz
  • 4,829
  • 1
  • 30
  • 25
0

The updated solution is:

1) Change your getAccount() method to use share:

getAccount () {
    // If we have account cached, use it instead
    if (this.accountObservable === null) {
        this.accountObservable = this._http.get('http://localhost/api/account')
            .pipe(share())
            .map(res => <Account> res.json().data)
            .catch(this.handleError);
    }

    return this.accountObservable;
}

2) Add import { share } from 'rxjs/operators'; to the top of your .ts file to get rid of the error on share.

Kyle Krzeski
  • 6,183
  • 6
  • 41
  • 52