Home > OS >  Is there any way to avoid nested subscribe?
Is there any way to avoid nested subscribe?

Time:12-04

(https://i.stack.imgur.com/2zm2w.png)

collectionData(queryRef).subscribe((data) => {
  for (const each of data) {
    this.getCourse(each.courseId)
      .pipe(take(1))
      .subscribe((courseData) => {
        const course = courseData[0];
        console.log(course);
        this.getLecturer(course.lecturerId).pipe(take(1)).subscribe((res: any)=>{
          const lecturer = res[0];
          course.lecturerName = lecturer.lecturerName;
          course.lecturerImageUrl = lecturer.lecturerImageUrl;
        });
        recentVisit.push(course);
      });
  }
});

Hi I am still new to the rxjs of Angular. I am building an Ionic app using Angular Fire. I'm currently facing some problems here, I'm using Firebase as my backend, and I would have to query through different collections to fetch my data. For example, the first subscription only fetch user course enroll data like courseId, progress..., the second subscription would fetch the course details, and the third will fetch lecturer details. Can anyone give some suggestion on how to avoid using nested subscription because many people said it is not recommended to do so. I would be very appreciated if you can provide some detailed explainations because I really only know the basics of rxjs.

I have tried concatMap but it shows firebase error(https://i.stack.imgur.com/6SOS0.png)]

collectionData(queryRef)
  .pipe(
    concatMap((res: any) => this.getCourse(res.courseId))
    //concatMap((result2: any) => this.getLecturer(result2.lecturerId))
  )
  .subscribe((res) => {
    console.log(res);
  });

But actually I also not sure did I did it right because I really cannot understand how concatMap works.

CodePudding user response:

I created a solution that prevents nested pipes as well as multiple explicit subscriptions by doing the following:

  • I combined concatMap and forkJoin
  • I outsourced part of the code to the helper-method getMergedCourseDetails() in order to keep the main pipe flat
/* Initialize all information about the courses */

ngOnInit(): void {
    this.collectionData(this.queryRef).pipe(
        concatMap(data => {
            if (data.length) {

                // Create an observable (backend-request) for each course-id:
                const courseObs = data.map(c => this.getCourse(c.courseId));

                // Execute the array of backend-requests via forkJoin():
                return courseObs.length ? forkJoin(courseObs) : of([]);
            }
            return of([]);
        }),
        concatMap((courseDataList: Course[][]) => {         
            if (courseDataList.length) {

                // Get the first course from each course array (as defined in SO question):
                const courses = courseDataList.filter(c => c.length).map(c => c[0]);

                // Create observables to retrieve additional details for each of the courses:
                const detailInfoObs = courses.map(c => this.getMergedCourseDetails(c));

                // Execute the created observables via forkJoin():
                return detailInfoObs.length ? forkJoin(detailInfoObs) : of([]);
            }
            return of([]);
        }),
        tap((courseList: Course[]) => {
            courseList.forEach(d => {
                console.log('Lecturer Id:', d.lecturerId);
                console.log('Lecturer Name:', d.lecturerName);
                console.log('Lecturer ImageUrl:', d.lecturerImageUrl);
            });
        })      
    )
    .subscribe();
}

/* Enrich existing course-data with lecturer-details */

private getMergedCourseDetails(course: Course): Observable<Course> {
    return this.getLecturer(course.lecturerId).pipe(                            
        map(lecturers =>            
            // Merge existing course-data with newly retrieved lecturer-details:            
            ({...course,
                lecturerName: lecturers[0]?.lecturerName ?? '', 
                lecturerImageUrl: lecturers[0]?.lecturerImageUrl ?? '' } as Course))
    );
}

CodePudding user response:

If you use nested Subscriptions, it means it would wait for the first the return a value, then call the second one and so one. Which costs alot of time. What you could use on this is forkJoin():

forkJoin(
 {
   a: this.http.call1()..
   b: this.http.call2()..
   c: this.http.call3()..
 }).subscribe()

forkJoins waits for all 3 Observables to emit once and gives you all the values. Example here: https://www.learnrxjs.io/learn-rxjs/operators/combination/forkjoin

  • Related