Danny Mencos
Danny Mencos

Reputation: 1295

Rxjs Observable: Get HTTP data only for the first subscriber and cached data for the rest

I'm trying to implement user permissions in an Angular app, but to put it simple, this is the scenario:

Upon loading a MainModule, I make an HTTP request and store the data in the localStorage, so the next time I can retrieve the data from there. This logic is inside an Observable, to which I'm subscribing from several places.

The problem is, if two subscriptions are executed at the same time, two HTTP request will be made before I even have the chance to store the data in the localStorage.

That's it, but to explain it further...

I'd like to make the HTTP request only once, store the data in the localStorage and emit the value, and from the second subscriber onwards, ignore all this logic if possible and just return the last emitted value.

I tried to use a BehaviorSubject, but then I get the last emitted data when I re-enter the module (i.e. user_A logs out and user_B logs in). The Service I'm using is provided in root, I couldn't provide it in MainModule because I get DI errors in some guards I have there. I also read about the share operator, but I'm not really sure about how to use it.

UPDATE (adding to @Nugu's Answer):

The problem with share was that the piped observable was not completing, so I wrapped a subscription to this.http.get<T> that calls .next(value) and .complete(), inside an Observable, and now it's working. I also added the condition of localStorage before making the HTTP request.

Also, on every module re-enter, I still got the last emitted value, so I turned the function getSomeData() into a property to always get the same instance of the Observable.

Upvotes: 2

Views: 861

Answers (2)

paranaaan
paranaaan

Reputation: 1735

I'm not sure is this is practical, but I like to share. first you need 1 more subject

 loggingIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(null);

Then inside login() service just trigger status like

 login(): void {
   this.loggingIn.next(true);
   // ....loging
   this.loggingIn.next(false);
 }

So getUser() go to be look like this

 getUser(): Observable<User> {
   return this.loggingIn.pipe(() => {
     return this.user.pipe(filter(user => user !== null));
   });
 }

Upvotes: 0

Nugu
Nugu

Reputation: 848

Use share() operator to share source among multiple subscribers. This will make sure the response is shared among the many subscribers and prevent duplicate HTTP request.

getSomeData(): Observable<any> {
  return this.http.get<any>('/endpoint').pipe(share());
}

You could do an if condition before calling getSomeData() to check if the data is already available in the store.

Upvotes: 3

Related Questions