Benny
Benny

Reputation: 278

Flattening Observable in Observables

I need to combine two Obersvables.

products
 ---1
    ---name: 'product 1'
    ---supplierId: '1'
 ---2
    ---name: 'product 2'
    ---supplierId: '2'

suppliers
 ---1
  ---name: 'supplier 1',
  ---contact: 'contact 1',     
 ---2
  ---name: 'supplier 2',
  ---contact: 'contact2'

In the end I wanna receive the following:

[
   {
     name: 'product 1'
     supplierId: '1',
     supplier: { name: 'supplier 1', contact: 'contact1'}
   },
   {
     name: 'product ´2'
     supplierId: '2',
     supplier: { name: 'supplier 2', contact: 'contact2'}
   },
]

At the moment I have the following code:

this.af.list('products')
  .map(products => {
    let items = [];
    (<Array<any>>products).forEach(p => {
      if (p.supplierId) {
        let supplier$ = this.af.object(`suppliers/${p.supplierId}`).take(1);

        items.push(Observable.forkJoin(Observable.of(p), supplier$, (p1, supplier) => {
          return {
            name: p1.name,
            supplier: supplier
          };
        }))
      }
    })
    return items;
  })
  .do(res => console.log('After Map', res))
  .flatMap(res => Observable.combineLatest(Observable.of(res)))
  .subscribe(res => console.log('Final Response', res))

Doesn't matter what I try, I either receive nothing or an Array of ForkJoinObservables.

Upvotes: 0

Views: 2101

Answers (2)

Julia Passynkova
Julia Passynkova

Reputation: 17859

Use scan operator. Keep products, suppliers and results in accumulator and join when you can. You might need also cleanup products and suppliers lists when you were able to create a result object( I did not do it)

Scan:

Applies an accumulator function over the source Observable, and returns each intermediate result, with an optional seed value.

http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-scan

  let o1$ = Rx.Observable.of({
      name: 'product 1',
      supplierId: '1'
    },
    {
      name: 'product 2',
      supplierId: '2'
    });

  let o2$ = Rx.Observable.of({
      name: 'supplier 1',
      contact: 'contact 1'
    },
    {
      name: 'supplier 2',
      contact: 'contact 2'
    });

  Rx.Observable.merge(o1$, o2$)
    .scan((acc, el) => {
        // product
        if (el.supplierId) {
          // check in supplier queue
          let supplier =
            acc.suppliers.find(supplier => supplier.name === ('supplier ' + el.supplierId));
          if (supplier) {
            acc.results.push({
              name: el.name,
              supplierId: el.supplierId,
              supplier: {name: supplier.name, contact: supplier.contact}
            });
          }
          else {
            acc.products.push(el);
          }
        }
        // supplier
        else {
          // check in product queue
          let product =
            acc.products.find(product => ('supplier ' + product.supplierId) === el.name);
          if (product) {
            acc.results.push({
              name: product.name,
              supplierId: product.supplierId,
              supplier: {name: el.name, contact: el.contact}
            });
          }
          else {
            acc.suppliers.push(el);
          }
        }
        return acc;
      },
      {suppliers: [], products: [], results: []}
    )
    .subscribe(({results}) => console.log(results));

Upvotes: 0

maxime1992
maxime1992

Reputation: 23793

There's no need to use flatMap nor combineLatest.

I've made a small Plunkr to demonstrate how to do that.

First of all, I didn't wanted to put too much code that has nothing to do with the problem and I've built my example without Angular, and using mocks :

Mock for data coming from the backend :

// raw objects that simulate the backend
const products = [
  {
    name: 'product 1',
    supplierId: '1'
  },
  {
    name: 'product 2',
    supplierId: '2'
  }

];

const suppliers = [
  {
    name: 'supplier 1',
    contact: 'contact 1'
  },
  {
    name: 'supplier 2',
    contact: 'contact2'
  }
];

Mock to simulate your Angular service (which returns an Observable) :

// simulate the angular service with HTTP
// use Observable.of and not Observable.from because we get the product list in one reponse
const getProducts = () => {
  return Observable.of(products).delay(1000);
};

const getSupplierByName = (name) => {
  const supplier = suppliers.find(s => s.name === `supplier ${name}`);

  return Observable.of(supplier).delay(1000);
};

And finally, the code to retrieve your data as expected :

// now let's try to get your data as you want, which is : 
// get the whole product list
// for each product, get his supplier
// merge the data into one big result
getProducts()
  .switchMap(products => 
    Observable
      .forkJoin(products
        .map(product => getSupplierByName(product.supplierId)
          .map(supplier => (Object.assign({}, product, { supplier })))))
  )
  .do(console.log)
  .subscribe();

Here's the result we get in our console :

enter image description here

Upvotes: 3

Related Questions