sentenza
sentenza

Reputation: 1730

How to combine an Observable of a sequence with an Observable of a single value and then flatten this structure

I want to find the best way possible to combine two functions that returns Observables.

The first function is defined as follows:

abstract getProfiles(): Observable<ClientProfile[]>

the second one is:

abstract getUser(userId: number): Observable<User>

I want to create a projection in order to create a data structure like this:

interface Projection {
  user: User,
  profiles: ClientProfile[]
}

in order to do that I would like to optimise this function

getUserProfiles() {
  this.userService.getProfiles()
    .pipe(
      flatMap((ps: ClientProfile[]) => {
         ps.map(p => this.userService.getUser(p.userId))
         // I NEED TO RETURN AN OBSERVABLE HERE:
         // {user: u, ...ps}
      })
    )
    .subscribe(_ => doSomething(_))

What's the best way to do merge a single user into the list of profiles?

Upvotes: 0

Views: 619

Answers (3)

Marlonchosky
Marlonchosky

Reputation: 526

I hope this can help you:

function solving() {
    interface Profile {
        id: number, name: string, userId: number
    }
    interface User {
        id: number, username: string
    }

    const getProfiles = () => ajax('api/profiles').pipe(
        pluck<AjaxResponse, Array<Profile>>('response')
    );

    const getUser = (id: number) => ajax(`api/users/${id}`).pipe(
        pluck<AjaxResponse, User>('response')
    );

    getProfiles().pipe(
        concatMap(perfils => from(perfils).pipe(map(p => [p, perfils]))),
        mergeMap(([p, perfils]: [Profile, Profile[]]) => 
                        getUser(p.userId).pipe(map(u => [u, perfils])))
    ).subscribe(console.log);
}

Upvotes: 0

JusMalcolm
JusMalcolm

Reputation: 1431

I would suggest combining the mergeMap and forkJoin operators.

import {map as rxMap, mergeMap} from 'rxjs/operators';
import {forkJoin} from 'rxjs';

this.userService.getProfiles().pipe(
    mergeMap(profiles =>
        forkJoin(profiles.map(profile =>
            this.userService.getUser(profile.userId).pipe(
                rxMap(user =>
                    ({
                        user,
                        profiles
                    })
                )
            )
        )
    )
)

The profiles list is being mapped to an Array<Observable<{user, profile}>>. These observables are completing (from the userService) when the http request returns.

ForkJoin waits for these observables to complete and then emits an observable containing these completed values. MergeMap unwraps this observable by merging the inner observable created by getUser() into the outer observable from getProfiles().

Upvotes: 1

user2216584
user2216584

Reputation: 5602

You can use switchMap to return an array of Observables of getUser() API like this:

getUserProfiles() {
    this.userService.getProfiles()
      .pipe(
        switchMap((ps: ClientProfile[]) => {
           const obs$ = ps.map(p => {
              return this.userService.getUser(p.userId)
                         .pipe(
                           map(user => ({user, profile: ps}))
                         );
           });

           return obs$;
           // I NEED TO RETURN AN OBSERVABLE HERE:
           // {user: u, ...ps}
        }),
        switchMap(x => x)
      )
      .subscribe(_ => doSomething(_))

Above solution will work fine if you do not want to wait for all the getUser() API to be finished. i.e. On subscribe you would process each emission of getUser() API response (which is projected with {user, profile}).

If you want to wait for all the getUser() API to be finished then use forkJoin() like this:

getUserProfiles() {
    this.userService.getProfiles()
      .pipe(
        switchMap((ps: ClientProfile[]) => {
           const obs$ = ps.map(p => {
              return this.userService.getUser(p.userId)
                         .pipe(
                           map(user => ({user, profile: ps}))
                         );
           });

           return forkJoin(obs$);
           // I NEED TO RETURN AN OBSERVABLE HERE:
           // {user: u, ...ps}
        }),
        tap(result => //result will be an array of {user, profile})
      )
      .subscribe(_ => doSomething(_))

Hope it helps.

Upvotes: 0

Related Questions