0

I have a function named fetchSeviceProviders which will return me a list of service providers and let me set the logo of the service providers by calling another service. I have achieved this by using two subscriptions.

At first subscription, I am getting the list. Then I am mapping it and calling the fetchServiceProviderLogo by all service providers' id.

Can this be done with one subscription? Is my method OK? If not, what is the problem? How can I do this by some rxjs operators?

I tried map and flatMap operator but couldn't get a clue.

fetchServiceProviders ()
{
    this.subscribers.fetchServiceProvidersLogoSub = this.serviceProviderService.fetchServiceProviders( new Map() ).subscribe(
        data =>
        {
            this.providers = data._embedded.serviceProviders;
            this.providers.map(
                provider =>
                {  
                    this.subscribers.fetchServiceProvidersLogoSub =
                        this.serviceProviderService.fetchServiceProviderLogo( { 'id': provider.id } ).subscribe(
                            logo =>
                            {
                                this.createIndividualLogoFromBlob( provider, logo );
                            }
                        );
                }
            );
        }
    )
}
Shofol
  • 693
  • 1
  • 11
  • 26
  • How many serviceProviders are there? – Zak Feb 19 '19 at 08:09
  • https://stackoverflow.com/questions/43336549/how-to-force-observables-to-execute-in-sequence https://stackoverflow.com/questions/48538562/how-to-do-sequence-maps-so-that-each-map-waits-for-the-previous-in-rxjs https://stackoverflow.com/questions/51212448/angular-make-multiple-http-calls-sequentially – martin Feb 19 '19 at 08:40
  • Do your `fetchServiceProviders` and `fetchServiceProviderLogo` Observables complete? Do they only emit one value or multiple? – frido Feb 19 '19 at 10:14
  • Possible duplicate of [How to force observables to execute in sequence?](https://stackoverflow.com/questions/43336549/how-to-force-observables-to-execute-in-sequence) – Robin Dijkhof Feb 19 '19 at 15:52

3 Answers3

1

If all your Observables complete and the order in that this.serviceProviderService.fetchServiceProviderLogo gets called doesn't matter you can use a combination of switchMap (or mergeMap) and merge. You also don't have to unsubscribe if your fetch requests all complete.

fetchServiceProviders () {
  this.serviceProviderService.fetchServiceProviders(new Map())
    .pipe(
      map(data => data._embedded.serviceProviders),
      mergeMap(providers => merge(
        providers.map(provider =>
          this.serviceProviderService.fetchServiceProviderLogo({ 'id': provider.id })
           .pipe(map(logo => ({ provider, logo })))
        )
      ))
    )
    .subscribe(({ provider, logo }) => this.createIndividualLogoFromBlob(provider, logo));
}

Instead of merge you could use forkJoin to execute your this.serviceProviderService.fetchServiceProviderLogo calls in parallel.

fetchServiceProviders () {
  this.serviceProviderService.fetchServiceProviders(new Map())
    .pipe(
      map(data => data._embedded.serviceProviders),
      mergeMap(providers => forkJoin(
        providers.map(provider =>
          this.serviceProviderService.fetchServiceProviderLogo({ 'id': provider.id })
           .pipe(map(logo => ({ provider, logo })))
        )
      ))
    )
    .subscribe(providerLogos =>
       providerLogos.forEach(({ provider, logo }) => this.createIndividualLogoFromBlob(provider, logo))
    );
}
frido
  • 13,065
  • 5
  • 42
  • 56
0

In your code, you are making a subscription for each serviceProvider. If you want to do all at once you can take a look at merge operator : https://www.learnrxjs.io/operators/combination/merge.html

Zak
  • 1,005
  • 10
  • 22
0

Here is a simple example. You can consider getItems and getStreetNameById your providers.

import { of, from } from 'rxjs';
import { switchMap, groupBy, map, mergeMap } from 'rxjs/operators';

function getItems() {
  return of([
    { streetId: 1, person: 'Person 1' },
    { streetId: 2, person: 'Person 2' },
    { streetId: 1, person: 'Person 3' },
  ]);
}

function getStreetNameById(id: number) {
  console.log('requesting street name for ', id);
  return of('Street ' + id);
}

getItems().pipe( //get all the items
  switchMap(items => from(items)), //create a new stream with each item from the list
  groupBy(item => item.streetId), //group by the streetId (we want to request the street name once per id
  mergeMap(group => { //merge all the transformed groups back into one stream
    return getStreetNameById(group.key) //request the street name by id
      .pipe(
        switchMap(streetName => group.pipe( //merge the street name back to all the items in the group
          map(item => ({ ...item, streetName })),
        ))
      );
  })
).subscribe(items => {
  console.log(items); //now all the items have the street name
});

https://stackblitz.com/edit/rxjs-merge-providers?file=index.ts

Andrei Tătar
  • 7,872
  • 19
  • 37