Búsqueda de sitios web

typescript - Mapear un Observable en elementos de array de otro Observable, aplanando el resultado


Este título probablemente necesita más explicación.

Básicamente, lo que recibo del backend es un Observable con un array de pilotos, y a cada uno de los elementos del array, quiero mapear otra propiedad isOnTrack, que consiste en otro Observable (booleano simple) que obtengo del backend. Quiero aplanar el resultado final para no tener un Observable dentro de otro Observable. He probado muchos de los operadores de rxjs, pero no puedo lograr que funcione.

Código que no funciona:

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()
);

Esto mapea con éxito el observable isOnTrack a los elementos del arreglo, pero no puedo conseguir que se aplane.

El proyecto está en RxJS 6

Actualización 1

Después de la respuesta de Jonathan, creo que debería haber usado la palabra desempaquetado en lugar de aplanar

El Observable después de las transformaciones que estaría buscando debería entregar algo similar a

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

y después de que un IsOnTrack se cambie en el backend, debería emitir el arreglo completo nuevamente.

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

Respuesta #1

Funciones de simulación de base de datos

// 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'},
])

Implementación

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

Explicación

El tipo de objeto que vive en los observables es simplemente <T> por la facilidad de no usar interfaces

  • solicitar todos los conductores de tu base de datos: requestDrivers$() => Observable
  • mapear la solicitud isOnTrack a cada conductor requestIsOnTrack$ (id) => Observable<Observable<T>[]>
  • limita tus actualizaciones de requestOnTrack a 1 usando take(1) => Observable<Observable<T>[]>
  • mapear los valores anteriores en el nuevo objeto ({...driver, isOnTrack}) => Observable<Observable<T>[]>
  • mergeAll para dividir tu arreglo en varias emisiones mergeAll() => Observable<Observable<T>>
  • combineAll para desempaquetar los observables y enlazar todos los valores mapeados a un arreglo combineAll() => Observable<T[]>

Aquí hay un stackblitz en funcionamiento