9

Angular2 Observable share is not working and duplicate http calls going

BuildingService.ts

@Injectable()
export class BuildingService {

constructor(private http: Http){       
  }

buildings$: Observable<Building[]>;
this.buildings: Building[];

getData() : Observable<Building[]>{
     this.buildings$ = this.http.get('http://localhost:8080/buildings').share().map(this.extractData);
     this.buildings$.subscribe(buildings => this.buildings = buildings);
     return this.buildings$;
  }

 private extractData(res: Response) {
    let body = res.json();
    return body;
} 

}

component1.ts

export class component1 {
constructor( private  buildingService: BuildingService) {}

this.subscription = this.buildingService.getData()
            .subscribe(buildings => console.log(buildings),
            error =>  this.errorMessage = <any>error);
}

component2.ts

export class component2 {
constructor( private  buildingService: BuildingService) {}

this.subscription = this.buildingService.getData()
            .subscribe(buildings => console.log(buildings),
            error =>  this.errorMessage = <any>error);
}

share is not working, multiple http calls are going. Even I tried code from this link

but no use.

Can somebody please let me know how to avoid duplicate http calls with Angular Observable?

Community
  • 1
  • 1
Pratap A.K
  • 4,337
  • 11
  • 42
  • 79

3 Answers3

24

I think this is just misunderstanding of what share() does.

When you call this.buildings$.subscribe(...) it makes a ConnectableObservable thanks to share() operator which is immediately followed by connect().

If you make another subscription while the HTTP request is pending it will just add another Observer to the ConnectableObservable and when the response is ready it'll be sent to both Observers. However if you let this.buildings$ to complete and after that you subscribe again it'll make another HTTP request because the ConnectableObservable is not connected to its source.

What you want instead is .publishReplay(1).refCount() (or shareReplay(1) since RxJS 5.4.0) that replays the last item emitted from the source. Very likely you'll also want to append take(1) to properly complete the chain.

martin
  • 93,354
  • 25
  • 191
  • 226
  • Thanks for the reply. publishReplay(1).refCount() will not even allow http call when I do getData() for the second time. Since I'm doing filtering on the returned data, I need to have exact one backend call when I do getData() all the time. Could you please let me know how to achieve this? – Pratap A.K Jan 03 '17 at 12:09
  • @PratapA.K Can you make pnlkr or jsbin reproducing your problem? – martin Jan 03 '17 at 12:39
  • creating pnlkr is quite difficult for me since I have multiple components and separate filter module. I have created a new question for my one more doubt. Could you please take a look?https://stackoverflow.com/questions/41458117/angular2-avoid-updating-reference-variable-also – Pratap A.K Jan 04 '17 at 07:26
  • 1
    @martin now you could use .shareReplay(1) which is an alias of .publishReplay(1).refCount() – Jakub Barczyk Oct 25 '17 at 08:03
  • @JakubBarczyk True – martin Oct 25 '17 at 08:05
3

You are creating a new stream with every .getData()-call here:

this.buildings$ = this.http.get('http://localhost:8080/buildings').share().map(this.extractData);

If you want to "share" data between components and prevent multiple rest-calls, you would have to most likely use the replay-feature of rxjs, you could for example do something like this:

@Injectable()
export class BuildingService {

constructor(private http: Http){}

buildings$: Observable<Building[]>;
this.buildings: Building[];

getData(fetchNew: boolean = false) : Observable<Building[]>{
     if (fetchNew || !this.buildings$) {
         this.buildings$ = this.http.get('http://localhost:8080/buildings')
             .map(this.extractData)
             .publishReplay(1)
             .refCount();
         this.buildings$.subscribe(buildings => this.buildings = buildings);
     }
     return this.buildings$;
  }

  private extractData(res: Response) {
    let body = res.json();
    return body;
  }
}

publishReplay(1) will re-emit("replay") the last emitted data to future subscribers, so no new call will be made.

olsn
  • 16,644
  • 6
  • 59
  • 65
  • how to have exact one backend call when I do getData(). I need to make backend call each time I call getData() method. – Pratap A.K Jan 03 '17 at 12:12
  • I understood your question that you do __not__ want to have multiple rest-calls to be made with every `getData()` ... you cannot call a function twice and say "but i just want to do that part once" - you could define how long the data should be kept (like here: http://stackoverflow.com/documentation/rxjs/8247/common-recipes/26490/caching-http-responses#t=201701031007534099407) - but I think for a _proper_ solution you should split up the logic of data-fetching and storing the data that will give you more flixibility - maybe you want to have a look at ngrx: https://github.com/ngrx/store – olsn Jan 03 '17 at 13:40
-3

You're subscribing to the observable in both the service and the components. Try this as the getData method in your service:

getData() : Observable<Building[]>{
 return this.http.get('http://localhost:8080/buildings')
    .map(this.extractData);
}
Dan O'Leary
  • 2,660
  • 6
  • 24
  • 50