1
votes

This might be a noob question, but I could find a reference on it.

I have two components placed in different places, each component uses a Service to query an endpoint and get some data (e.g. user profile).

The service returns an observable, I need the observable to be unique or at least to make a unique request.

// Service, shared across multiple components
@Injectable()
export class SomeService {
    getData(): Observable<DataModel> {
        return this._http.get<DataResponse>('/some-route').pipe(
          map((response: DataResponse) => this.handleResponse(response)),
          share() // is this right ?
        );
    }

}

// Component requesting data
@Component({ selector: '...', ... })
export class FirstComponent implements OnInit {
  constructor(private _service: SomeService) { }

    ngOnInit() {
        this._service.getData().subscribe(
          data => {
            console.log(data);
          }
        );
      }
}

// Another component requesting the same data
@Component({ selector: '...', ... })
export class SecondComponent implements OnInit {
  constructor(private _service: SomeService) { }

    ngOnInit() {
        this._service.getData().subscribe(
          data => {
            console.log(data);
          }
        );
      }
}

The service works and it gets the data, but the request is sent twice, I only want a single request to be sent. The components live at the same time (let say one at the top and the second at the bottom of the screen). So they make the request simultaneously.

Is there a way the service only sent one request.

And BTW, the first request has a status of 200 and the second 304.

Thanks.

UPDATE

Possible solution

So far I managed by adding a Service variable

private _observable: Observable<DataModel>;

Then when getting the data

getData(): Observable<DataModel> {
    if (!this._observable) {
        this._observable = this._http.get<DataResponse>('/some-route').pipe(
          map((response: DataResponse) => this.handleResponse(response)),
          // HERE IS THE TRICK
          publishLast(),
          refCount()
        )
    }

    return this_observable;
}

The trick is using publishLast and refCount

Any better way/idea ?

4
If you are noob - take a look at my video course for RxJS beginners: packtpub.com/web-development/hands-rxjs-web-development-video - Alexander Poshtaruk

4 Answers

2
votes

I have found the following pattern to be effective for doing this:

@Injectable()
export class SomeService {
  myDataObservable$: Observable<DataModel>;

  constructor( ) {
    this.myDataObservable$ = this.getData().pipe(shareReplay());
  }

  getData(): Observable<DataModel> {
    return this._http.get<DataResponse>('/some-route').pipe(
      map((response: DataResponse) => this.handleResponse(response))
    );
  }
}
  1. Do a property (myDataObservable$) on the service that holds the observable that will be used by multiple components.
  2. Have a method (getData()) that does the http request.
  3. In the constructor, call the method and add shareReplay().
  4. In the components you use it with "this.someService.myDataObservable$.subscribe(...)"

With this pattern no http request will be sent before you have called subscribe on the myDataObservable$ in at least one component. After the first component has called subscribe, all following subscriptions will use the value that is already there, so you will not have more than one http request.

1
votes

Take a look at my library ngx-rxcache. It simplifies the managing of state.

https://github.com/adriandavidbrand/ngx-rxcache

@Injectable()
export class SomeService {
 private dataCache = this.cache.get<DataModel>({
    id: 'some uniquie id',
    load: true,
    construct: () => this.http.get<DataResponse>('/some-route').pipe(
      map((response: DataResponse) => this.handleResponse(response))
    )
 });

 data$ = this.dataCache.value$;

 constructor(cache: RxCacheService, http: HttpClient) {}
}

Then you can access the data on the service with

this.service.data$;

https://medium.com/@adrianbrand/angular-state-management-with-rxcache-468a865fc3fb

0
votes

you can use shareReplay instead, that will replay the event from the source observable to all subscribers and make sure you share the last result only

 shareReplay(1)

so you code will look like this

getData(): Observable<DataModel> {
    return this._http.get<DataResponse>('/some-route').pipe(
      map((response: DataResponse) => this.handleResponse(response)),
      shareReplay(1) 
    );
}
-1
votes

You can easily group your requests into one Observable that will resolve at the completion of all your requests using forkJoin Operator

here is an example

  public requestDataFromMultipleSources(): Observable<any[]> {
    let response1 = this.http.get(requestUrl1);
    let response2 = this.http.get(requestUrl2);
    let response3 = this.http.get(requestUrl3);
    // Observable.forkJoin (RxJS 5) changes to just forkJoin() in RxJS 6
    return forkJoin([response1, response2, response3]);
  }

In your example you should make the requests in the father component. And then inject the result to the children components