Patrik Jajo Jajcay
Patrik Jajo Jajcay

Reputation: 73

Rxjs - remap object with observables as values

I got an observable like this:

  const original = Observable.of({
    a: this.http.get('https://jsonplaceholder.typicode.com/todos/11'),
    b: this.http.get('https://jsonplaceholder.typicode.com/todos/22'),
    c: this.http.get('https://jsonplaceholder.typicode.com/todos/33')
  });

I need to resolve the inner observables and get something like this in the subscribe handler:

  {
    a: ResponseFromServer,
    b: ResponseFromServer,
    c: ResponseFromServer,
  }

How should I approach this problem?

Thanks.

EDIT: I've figured it out, read below.

It seems that not many people know that *Map operators used to have something called resultSelector as their second argument. Now in rxjs v6, you can do the same with inner map, let me show you.

const original = Observable.of({
    a: this.http.get('https://jsonplaceholder.typicode.com/todos/11'),
    b: this.http.get('https://jsonplaceholder.typicode.com/todos/22'),
    c: this.http.get('https://jsonplaceholder.typicode.com/todos/33')
});

const transformed = original.pipe(
    mergeMap(sourceValue => 
        forkJoin(_.values(sourceValue)).pipe(map(resolvedHttpRequests => {
            // here you have access to sourceValue as well as resolvedHttpRequests so you can do manual join to match the data.
        }))
    )
)

Upvotes: 5

Views: 2444

Answers (3)

Žanas Stundys
Žanas Stundys

Reputation: 44

Update for 2020

forkJoin(
  // as of RxJS 6.5+ we can use a dictionary of sources
  {
    google: ajax.getJSON('https://api.github.com/users/google'),
    microsoft: ajax.getJSON('https://api.github.com/users/microsoft'),
    users: ajax.getJSON('https://api.github.com/users')
  }
)
  // { google: object, microsoft: object, users: array }
  .subscribe(console.log);

https://www.learnrxjs.io/learn-rxjs/operators/combination/forkjoin

Upvotes: 2

joh04667
joh04667

Reputation: 7427

They way you're doing it above (with Observable.of) is essentially creating a higher-level Observable of lower Observables.

I think a better operator would be forkJoin, as each of these Observables are cold and finite and forkJoin captures the first emission of each Observable and emits all values when all are complete:

  const original = forkJoin([
    this.http.get('https://jsonplaceholder.typicode.com/todos/11'),
    this.http.get('https://jsonplaceholder.typicode.com/todos/22'),
    this.http.get('https://jsonplaceholder.typicode.com/todos/33')
  ]);

  let result = {a: null, b: null, c: null};

  original.subscribe([a,b,c] => result = {a,b,c});

Just note that the emitted item from forkJoin will be an array whose index match the indices of the Observables passed in.

Upvotes: 1

martin
martin

Reputation: 96889

If the source object already contains Observables you can do it for example like the following (there are obviously multiple ways to do this):

const mockXHRCall = (url: string) => {
  return of('response');
};

const original = of({
  a: mockXHRCall('https://jsonplaceholder.typicode.com/todos/11'),
  b: mockXHRCall('https://jsonplaceholder.typicode.com/todos/22'),
  c: mockXHRCall('https://jsonplaceholder.typicode.com/todos/33')
}).pipe(
  mergeMap(obj => {
    const observables$ = Object.keys(obj).map(key => obj[key].pipe(
      map(response => (
        { [key]: response } // wrap the response with eg. { c: ResponseFromC }
      )),
    ));

    return merge(...observables$);
  }),
  scan((acc, wrapped) => (
    { ...acc, ...wrapped }
  ), {}),
  takeLast(1),
).subscribe(console.log);

The scan collects all responses and merges them into a single object.

Live demo: https://stackblitz.com/edit/rxjs-ffza8b

Upvotes: 1

Related Questions