2

This title probably needs more explanation.

Basically what I get from the backend is an Observable with an array of racedrivers, and to each of the array items, I want to map another property isOnTrack, which consists of another Observable (simple boolean) I retrieve from the backend. I want to flatten the end result so I don't have an Observable within an Observable. I've tried many of the rxjs operators but I cannot get it to work.

Code that doesn't work:

this.drivers$ = this.db.list('users').valueChanges().pipe(
  map(arr => arr.map( (driver:any) => {
    driver.isOnTrack = this.db.object(`telemetry/${driver.uid}/values/IsOnTrack`).valueChanges();
     return driver
  })),
  mergeAll()
);

This successfully maps the isOnTrack observable to the array items but I can't get it flattened.

Project is on RxJS 6

Update 1

After Jonathan's answer I believe I should have used the word unpacked instead of flattening

The Observable after transformations that I would be looking for should deliver something similar to

of([
  {id: 1, name: 'foo', isOnTrack: true},
  {id: 2, name: 'bar', isOnTrack: true},
  {id: 3, name: 'baz', isOnTrack: false},
])

and after one IsOnTrack is changed in the backend it should emit the complete array again.

of([
  {id: 1, name: 'foo', isOnTrack: false},
  {id: 2, name: 'bar', isOnTrack: true},
  {id: 3, name: 'baz', isOnTrack: false},
])
Hamada
  • 1,836
  • 3
  • 13
  • 27

1 Answers1

1

Mock db functions

// this.db.list('users').valueChanges()
const requestIsOnTrack$ = (id: number): Observable<boolean> => interval(1000).pipe(
  take(3),
  map(() => Math.random() >= 0.5)
)

// this.db.object(`telemetry/${driver.uid}/values/IsOnTrack`).valueChanges()
const requestDrivers$ = () => of([
  {id: 1, name: 'foo'},
  {id: 2, name: 'bar'},
  {id: 3, name: 'baz'},
])

Implementation

const drivers$ = requestDrivers$().pipe(
  map(drivers => drivers.map(driver => requestIsOnTrack$(driver.id).pipe(
    take(1),
    map(isOnTrack => ({
      ...driver,
      isOnTrack
    }))
  ))),
  mergeAll(),
  combineAll()
)

Explanation

The object type living in the observables is just <T> for the ease of not using interfaces

  • request all drivers from your db: requestDrivers$() => Observable
  • map the request isOnTrack to each driver requestIsOnTrack$(id) => Observable<Observable<T>[]>
  • limit your requestOnTrack updates to 1 by using take(1) => Observable<Observable<T>[]>
  • map the previous values into the new object ({...driver, isOnTrack}) => Observable<Observable<T>[]>
  • mergeAll to split up your array into several emits mergeAll() => Observable<Observable<T>>
  • combineAll to unbox the observables and bind all mapped values to an array combineAll() => Observable<T[]>

Here is a running stackblitz

Jonathan Stellwag
  • 3,843
  • 4
  • 25
  • 50
  • Hello Jonathan, thanks a lot for your answer and the time invested. Although your answer is probably exactly what I asked for, the final observable is not really what I was looking for due to my poor wording. The final observable should still be an array of the drivers, just with the unpacked IsOnTrack Observable in it. – Peter ten Klooster Jun 13 '20 at 06:02
  • @PetertenKlooster no problem. Can happen during a development phase. I updated my answer so you get an array. Two things to keep in mind. 1. I used take(1) so that requestIsOnTrack$ does not update result all the time. Change this if you don't want it. 2. Through the usage of combineAll you need to wait until every isOnTrack has at least emitted one value (for each driver), before the final result emits. – Jonathan Stellwag Jun 13 '20 at 06:58
  • 1
    Hi Jonathan, this works great! thanks a lot. I have immediate access to all the values so thats not a problem. – Peter ten Klooster Jun 13 '20 at 16:47
  • I'm glad I could help :) – Jonathan Stellwag Jun 13 '20 at 19:38