How to convert two observable items to another observable object with mapped values of two initial observables?

Viewed 93

These are the input observables:

items$ = of([
  {name: 'John', age: 30, job: 'Dentist', address: {street: 'Maple (6,103)', country: 'US'},
  {name: 'Mark', age: 34, job: 'Driver', address: {street: 'Pine (6,170)', country: 'US'}},
  {name: 'Johannes', age: 25, job: 'Mechanic', address: {street: 'Elm (5,233)', country: 'US'}},
  {name: 'Thjoma', age: 28, job: 'Coder', address: {street: 'Lake (4,901)', country: 'US'}},
])

fields$ = of([
  name, age, street
])

How to map above observables fields to a new object for each item and return new array?

Expected Result:

of([
  {name: 'John', age: 30, job: 'Dentist', street: 'Maple (6,103)'},
  {name: 'Mark', age: 34, job: 'Driver', street: 'Pine (6,170)'},
  {name: 'Johannes', age: 25, job: 'Mechanic', street: 'Elm (5,233)'},
  {name: 'Thjoma', age: 28, job: 'Coder', street: 'Lake (4,901)'},
])

See the inside items (street) are mapped/ flattened to get new object for each items.

I've tried out something like this: But it is not working.

    return combineLatest(
      this.items$,
      this.fields$,
      (items, fields) => ({ items, fields })
    ).pipe(
      map(({ items, fields }) =>
        fields.reduce(
          (acc, cur) => ({ ...acc, [cur]: items.map((item) => item[cur]) }),
          {}
        )
      )
    );

Can someone help?

SEE THAT

Only fields from the observable fields$ is appearing in the resulting observable. Others are omitted.

1 Answers

Something like this could work: StackBlitz

const $specifiedFieldsOnly = combineLatest(this.items$, this.fields$).pipe(
  map(([items, fields]) => 
    items.map(item => (
      fields.reduce((obj, field) => ({ ...obj, [field]:item[field] }), {}))
    )
  )
);

Of course it will only work for field names that exist in the source data. In your case street will not work because your objects don't have a street property. You want to dig out the address.street property, so you have some work to do in order to make that work.

If you wanted to specify your field names with a dot to denote a nested property (e.g. address.street), this could work: StackBlitz

const fields$ = of([
  'name', 'age', 'address.street', 'address.country'
]);

const $specifiedFieldsOnly = combineLatest(this.items$, this.fields$).pipe(
  map(([items, fields]) => 
    items.map(item => (
      fields.reduce((obj, path) => {
        const field = path.split('.').reduce(
          (acc, cur, i) => { 
            const name = cur;
            const value = (i === 0) ? item[cur] : acc.value[cur];

            return { name, value };
          },
          { name: undefined, value: undefined }
        );

        return { ...obj, [field.name]: field.value };

      }, {}))
    )
  )
);

Note: you'd need to be a little more elaborate if you're concerned about duplicate field names.

Related