问题描述
我正在使用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)}
})
})
)
)
)
}),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数组的长度进行测试。问题是当我尝试建立条件时出现以下错误
类型'(dates:SessionTaskDate [])=> void'的参数不能分配给类型'(value:SessionTaskDate [],index:number)=> ObservableInput'的参数。 不能将类型“ void”分配给类型“ ObservableInput”。
触发以下内容:
switchMap(dates =>{
if(dates.length > 0){
dates.map(date => this.augmentDateWithTask(date))
}
})
解决方法
如果我理解正确,您有一系列应用,它们由this.applicationService.list
返回的Observable通知。
然后每个应用程序具有一个 courseSessionId ,您可以使用它们通过this.sessionService.get
方法获取课程会话详细信息。
然后每个 session 都有一个 courseId ,您可以使用它们通过this.courseService.get
获取 course 的详细信息。
然后每个课程都有一个 fieldId ,您可以通过this.fieldService.get
来获取 field 的详细信息。
现在,您应该有一个 session 数组,其中还包含它们所引用的 course 的所有详细信息。
然后,对于每个会话,您需要通过this.TaskdatesService.getBySessionId
获取日期。
日期似乎是包含 taskDatesId 的对象。您会收集所有 taskDatesIds ,并通过this.tasksService.get
获取 task 详细信息。
现在您已经拥有所有日期和所有任务,对于每个 date ,您都将创建一个带有 date 的新对象>属性及其相关的任务。
然后返回 session ,创建一个具有所有 session 属性及其相关的 dates 的新对象。
现在您有了一个包含所有 session 详细信息,它所引用的 course 所有详细信息以及它的 dates 所有详细信息的对象有。
最后一步是为每个 application 创建一个新对象,该对象包含 application 的所有属性以及“增强的” session 刚创建的对象。
很合理。
因此,再次,如果这是正确的理解,我将从最内在的要求向外解决问题。
第一个内部请求是一个从 course 开始返回一个Observable的请求,该对象发出具有所有 course 属性和 field 的对象>详细信息(我们将此对象称为 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 。不同的应用可以共享相同的 course ,因此具有相同的 courseSessionId 。因此,获取所有 application ,创建唯一的 courseSessionIds 列表,使用此类列表获取所有 courses 然后分配给每个课程都是有意义的应用及其课程。通过这种方式,我们避免了针对同一课程多次查询后端。
可以这样实现
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}
});
})
)
})
)
}
使用类似的样式,您应该还可以在会话中添加日期详细信息。同样在这种情况下,我们从最内部的操作开始,即通过his.tasksService.get
给定 taskIds 的数组来检索 tasks 的数组。
代码看起来像这样
fetchTasks(taskIds) {
taskObs = taskIds.map(tId => this.tasksService.get(tId));
return forkJoin(taskObs)
}
向外移动一步,我们可以用 date 的 task 扩展 date 数组中的每个 date / em>这样
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
方法,并添加带有 date 信息的 session 扩展部分
fetchAugmentedSession(courseSessionId) {
return this.sessionService.get(courseSessionId).pipe(
switchMap(session => this.augmentSessionWithCourse(session)),switchMap(session => this.augmentSessionWithDates(session)),)
}
通过这种方式,您可以将逻辑分成较小的块,这些块更易于测试,并且希望更易于阅读。
我没有任何游乐场可以测试代码,因此很可能其中有错别字。我希望逻辑很清楚。
,有很多异步嵌套,也许还有另一种方法可以解决这个问题?
如果您尝试获取多个可观测对象并进行单个订阅(例如,能够以类似方式对多个事件做出反应),请使用rxjs merge。
如果您要进行很多单独的订阅,例如订阅用户,foo和bar,然后将其所有值用于某种逻辑,请使用rxjs forkJoin。
如果您声明一个明确的目标,则提供指导会比较容易,但是我知道rxjs运算符嵌套的级别很难维护。