简体   繁体   中英

RxJS: Modify Observable array before subscribing

I'm fetching data ( students via getStudents() ) from an API which returns an Observable. Within this result I need to get data from two different tables and combine the result.

Here are my simplified interfaces:

export interface student Student {
   id: string
   name: string,
   school_id: string,
   classroom_id: string
}

export interface school School {
   id: string,
   name: string
}

export interface classroom Classroom {
   id: string,
   name: string
}

I now need to fetch all students and add the respective school and classroom for each student via the foreign keys school_id and classroom_id .

My current approach is something like the following. I know it's unfinished but I can't manage to find the right operator and how to properly use it.

this.getStudents().pipe(
   switchMap(students => {
      student.map(student => forkJoin([
         this.getSchool(student.school_id),
         this.getClassroom(student.classroom_id)
      ]))
   })
)

All of the methods ( getStudents() , getSchool() , getClassroom() ) return Observables. My goal is to get an array of students with the respective school and classroom data after subscription.

I know how to get it done if I need to fetch a single student (eg with getStudent() ) and then use combineLatest to combine multiple streams. But when fetching multiple students it's different.

Thank you in advance!

You need to forkJoin the observable array that you get from student.map() and use map to project the result into the required object.

const result$ = getStudents().pipe(
  switchMap((students: Student[]) =>
    forkJoin(students.map(student =>
      forkJoin([
        getSchool(student.school_id),
        getClassroom(student.classroom_id)
      ]).pipe(map(([school, classroom]) => ({
        student,
        school,
        classroom
      }))
    )
  ))
));

What you can do is to covert the original array result to a stream emitting each student and the construct each record one by one. Once the object is built, you can return it back to a single array result using toArray() . I find doing things this way (flattening a stream that emits arrays to a stream that emits individual elements) easier to manage due to the reduced nesting.

this.getStudents().pipe(
  switchMap(students => students), // emit each element individually
  concatMap(student => // construct the student object.
    forkJoin({
      classRoom: this.getClassroom(student.classroom_id),
      school: this.getSchool(student.school_id),
      student: of(student)
    })
  ),
  toArray() // when all results are built, emit one array result.
);

This technique works best if getStudents() returns a single emission and then completes. If the observable stays open, then toArray() will not emit as it only executes when the source observable completes.

If it does stay open, but you only want the first array emission then adding take(1) or first() at the start of the pipe will do work.

StackBlitz

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM