0

我正在joined$使用switchMap和加入一个全局可观察对象中的多个可观察对象combineLatest

这是组件 TS 文件

export class ProgressComponent implements OnInit{

    user: User;
    joined$: Observable<any>;

    constructor(
        protected tasksService: TasksService,
        protected courseService: CoursesService,
        protected fieldService: FieldsService,
        protected sessionService: SessionsService,
        protected applicationService: ApplicationForSessionsService,
        protected TaskdatesService: SessionTaskDatesService,
        protected router: Router,
        private userStore: UserStore)
    {}

    ngOnInit(): void {
        this.user = this.userStore.getUser();
        this.joined$ = this.applicationService.list(1, 10, undefined, this.user.id)
            .pipe(
                switchMap(applications => {
                    const courseSessionIds = uniq(applications.map(application => application.courseSessionId))

                    return combineLatest(
                        of(applications),
                        combineLatest(
                            courseSessionIds.map(courseSessionId => this.sessionService.get(courseSessionId).pipe(
                                switchMap(session => {
                                    const courseId = session.courseId
                                    return combineLatest(
                                        of(session),
                                        combineLatest(
                                            this.courseService.get(courseId).pipe(
                                                switchMap(course => {
                                                    const fieldId = course.fieldId

                                                    return combineLatest(
                                                        of(course),
                                                        combineLatest(
                                                            this.fieldService.get(fieldId).pipe(
                                                                map (field => field)
                                                            )
                                                        )
                                                    )
                                                }),
                                                map(([course, field]) => {
                                                    return {...course, field: field.find(f => f.id == course.fieldId)}
                                                })
                                            )
                                        ),
                                                                         
                                    )
                                }),
                                map(([session, course]) =>  {
                                    return {
                                        ...session, 
                                        course: course.find(c => c.id === session.courseId)
                                    }
                                }),
                                switchMap( session => {
                                    const sessionId = session.id;

                                    return combineLatest(
                                        of(session),
                                        combineLatest(
                                            this.TaskdatesService.getBySessionId(sessionId).pipe(
                                                switchMap(dates => {
                                                    const taskDatesIds = uniq(dates.map(dt => dt.taskId));

                                                    return combineLatest(
                                                        of(dates),
                                                        combineLatest(
                                                            taskDatesIds.map(taskDateId => this.tasksService.get(taskDateId).pipe(
                                                                map(task => task)
                                                            ))
                                                        )
                                                    )
                                                }),
                                                map(([dates, task]) => {
                                                    return dates.map(date => {
                                                        return {...date, task: task.find(t => t.id === date.taskId)}
                                                    })
                                                
                                                })
                                            )
                                        )
                                    )
                                }),
                                map(([session, dates]) => {
                                    return {
                                        ...session,
                                        dates: dates.map(date => date.find(d => d.sessionId === session.id))
                                    }
                                })
                            ))
                        )
                    )
                }),
                map(([applications, session]) => {
                    return applications.map(app => {
                        return {
                            ...app,
                            session: session.find(s => s.id === app.courseSessionId)
                        }
                    })
                })
            );
    }
}

这是HTML模板文件

    <ng-container *ngIf="joined$ | async; else loading; let joined">

    <div *ngFor="let application of joined">
        <div class="current-application">
            <nb-card>
                <nb-card-header>
                    <h4>{{application.session.course.name}}</h4>
                    <small><i>{{application.session.course.field.name}}</i></small>
                </nb-card-header>
                <nb-card-body>
                    <p><b>id: </b>{{application.id}}</p>
                    <p><b>applicationDate: </b>{{application.applicationDate}}</p>
                    <p><b>acceptedDate: </b>{{application.acceptedDate}}</p>
                    <hr>
                    <p><b>session Id: </b>{{application.session.id}}</p>
                    <p><b>session capacity: </b>{{application.session.capacity}}</p>
                    <p><b>session startDate: </b>{{application.session.startDate}}</p>
                    <p><b>session endDate: </b>{{application.session.endDate}}</p>
                    <hr>
                    <p><b>Course Id: </b>{{application.session.course.id}}</p>
                    <p><b>Course Id: </b>{{application.session.course.id}}</p>
                </nb-card-body>
            </nb-card>
        </div>
    </div>

</ng-container>

<ng-template #loading>
    <p>Loding ...</p>
</ng-template>

编辑:调试后我发现当日期数组为空时会发生错误,因此解决方案包括对日期数组的长度进行测试。问题是当我尝试创建条件时出现以下错误

'(dates: SessionTaskDate[]) => void' 类型的参数不能分配给 '(value: SessionTaskDate[], index: number) => ObservableInput' 类型的参数。类型“void”不可分配给类型“ObservableInput”。

在以下情况下触发:

switchMap(dates =>{
        if(dates.length > 0){
            dates.map(date => this.augmentDateWithTask(date))
        }
    })
4

2 回答 2

2

如果我理解正确,您有一系列应用程序,这些应用程序由返回的 Observable 通知this.applicationService.list

然后每个应用程序都有一个courseSessionIdthis.sessionService.get ,您可以使用它通过方法获取课程会话详细信息。

然后每个会话都有一个courseId,您可以使用它来通过 获取课程详细信息this.courseService.get

然后每门课程都有一个fieldId,您可以使用它来获取字段详细信息this.fieldService.get

现在你应该有一个会话数组,其中还包含他们所引用的课程的所有详细信息。

然后,对于每个session,您需要通过获取日期this.TaskdatesService.getBySessionId

日期似乎是包含taskDatesId的对象。您收集所有taskDatesIds并通过 获取任务详细信息this.tasksService.get

现在您有了所有日期和所有任务,为每个日期创建一个具有日期属性的新对象及其相关任务

然后您回到会话,您创建一个包含所有会话属性及其相关日期的新对象。

现在您有了一个对象,其中包含所有会话详细信息、它所引用的课程的所有详细信息以及它所拥有的所有日期的详细信息。

最后一步是为每个应用程序创建一个新对象,其中包含应用程序的所有属性以及刚刚创建的“增强”会话对象的所有属性。

有点逻辑。

所以,再次,如果这是正确的理解,我会从最内在的请求向外处理问题。

第一个内部请求是从课程开始返回一个 Observable 的请求,该 Observable 发出一个包含所有课程属性和字段详细信息的对象(让我们将此对象称为augmentedCourse)。这可以通过这样的方法执行

augmentCourseWithField(course) {
  return this.fieldService.get(course.fieldId).pipe(
    map (field => {
      return {...course, field}
    })
  )
}

然后我会向外迈出一步,并创建一个方法,从courseId开始,返回一个 Observable ,它发出一个augmentedCourse,即一个包含所有课程字段详细信息的对象。

fetchAugmentedCourse(courseId) {
  return this.courseService.get(courseId).pipe(
     switchMap(course => this.augmentCourseWithField(course))
  )
}

现在让我们再进一步,创建一个方法,从session开始,返回一个对象,该对象包含 session 的所有属性以及 session引用augmentedCourse的属性。我们称这个对象为 augmentedSession

augmentSessionWithCourse(session) {
  return this.fetchAugmentedCourse(session.courseId).pipe(
    map(course => {
      return {...session, course}
    })
  )
}

现在又往外迈了一步。我们想从courseSessionId开始获取augmentedSession

fetchAugmentedSession(courseSessionId) {
  return this.sessionService.get(courseSessionId).pipe(
     switchMap(session => this.augmentSessionWithCourse(session))
  )
}

那么,到目前为止,我们取得了什么成就?我们可以创建一个 Observable,它从courseSessionId开始发出一个augmentedSession

在最外层,虽然我们有一个应用程序列表,每个应用程序都包含一个courseSessionId。不同的应用程序可以共享相同的课程,因此具有相同的courseSessionId因此,获取所有应用程序、创建唯一courseSessionIds列表、使用该列表获取所有课程然后将其课程分配给每个应用程序是有意义的。通过这种方式,我们避免了对同一课程多次查询后端。

可以这样实现

fetchAugmentedApplications(user) {
  return this.applicationService.list(1, 10, undefined, user.id).pipe(
    switchMap(applications => {
      const courseSessionIds = uniq(applications.map(application => application.courseSessionId));
      // create an array of Observables, each one will emit an augmentedSession
      const augSessObs = courseSessionIds.map(sId => fetchAugmentedSession(sId))
      // we can use combineLatest instead of forkJoin, but I prefer forkJoin
      return forkJoin(augSessObs).pipe(
        map(augmentedSessions => {
          return applications.map(app => {
            const appSession = augmentedSessions.find(s => s.id === app.courseSessionId);
            return {...app, session: appSession}
          });
        })
      )
    })
  )
}

使用类似的样式,您应该还可以在会话中添加日期详细信息。同样在这种情况下,我们从最里面的操作开始,即通过给定一个taskId数组来检索一个任务数组。his.tasksService.get

代码看起来像这样

fetchTasks(taskIds) {
   taskObs = taskIds.map(tId => this.tasksService.get(tId));
   return forkJoin(taskObs)
}

向外移动一步,我们可以使用属于该日期任务来增加日期数组中的每个日期,如下所示

augmentDates(dates) {
  if (dates.length === 0) {
    return of([]);
  }
  const taskDatesIds = uniq(dates.map(dt => dt.taskId));
  return fetchTasks(taskDatesIds).pipe(
    map(tasks => {
      return dates.map(date => {
        return {...date, task: task.find(t => t.id === date.taskId)}
      })
    })
  )
}

现在我们可以像这样用它的日期来增加一个会话

augmentSessionWithDates(session) {
  return this.TaskdatesService.getBySessionId(session.id).pipe(
    switchMap(dates => augmentDates(dates).pipe( 
      map(augmentedDates => {
        return {...session, dates: augmentedDates};
      })
    ))
  )
}

我们现在可以完成该方法,还添加使用日期信息fetchAugmentedSession增加会话的部分,如下所示

fetchAugmentedSession(courseSessionId) {
  return this.sessionService.get(courseSessionId).pipe(
     switchMap(session => this.augmentSessionWithCourse(session)),
     switchMap(session => this.augmentSessionWithDates(session)),
  )
}

通过这种方式,您将逻辑拆分为更小的块,这些块更易于测试,并且希望更易于阅读。

我没有任何操场来测试代码,所以很可能有错别字。我希望逻辑足够清楚。

于 2020-09-01T08:30:57.600 回答
1

这是很多异步嵌套,也许还有另一种方法可以解决这个问题?

如果您尝试获取多个 observables 并拥有一个订阅(例如,能够以类似的方式对多个事件做出反应),那么使用 rxjs合并。

如果你想做很多单独的订阅,比如订阅用户、foo 和 bar,然后将它们的所有值用于某些逻辑,请使用 rxjs forkJoin。

如果你陈述一个明确的目标,那么提供指导会更容易,但我知道 rxjs 运算符嵌套的级别不容易维护。

于 2020-09-01T00:59:37.320 回答