I'm joining multiple observables in one global joined$
observable using switchMap
and combineLatest
this is the component TS file:
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)
}
})
})
);
}
}
And here is the HTML template file
<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>
Edit: After debugging I found that the error occurs when Dates array is empty, so the solution consists of applying a test for the length of the dates array. The problem is when I try to make a condition I got the following error
Argument of type '(dates: SessionTaskDate[]) => void' is not assignable to parameter of type '(value: SessionTaskDate[], index: number) => ObservableInput'. Type 'void' is not assignable to type 'ObservableInput'.
triggered in the following:
switchMap(dates =>{
if(dates.length > 0){
dates.map(date => this.augmentDateWithTask(date))
}
})
If I understand right, you have a series of applications which are notified by the Observable returned by
this.applicationService.list
.Then each application has a courseSessionId which you can use to fetch course session details via
this.sessionService.get
method.Then each session has a courseId which you can use to fetch course details via
this.courseService.get
.Then each course has a fieldId which you can use to fetch field details via
this.fieldService.get
.Now you should have an array of sessions containing also all details of the course they refer to.
Then, for each session, you need to fetch the dates via
this.TaskdatesService.getBySessionId
.Dates seems to be objects containing a taskDatesId. You collect all taskDatesIds and fetch task details via
this.tasksService.get
.Now that you have all dates and all tasks, for each date you create a new object with date properties and its related task.
Then you go back to the session you create a new object with all session properties and its related dates.
Now you have an object with all session details, all details of the course it refers to and all the details of the dates it has.
The last step is to create, for each application, a new object containing all properties of the application plus all properties of the "augmented" session object just created.
Quite some logic.
So, again, if this is the correct understanding, I would approach the problem from the most inner requests outwards.
The first inner request is the one that, starting from a course returns an Observable which emits an object with all course properties and the field details (let's call this object an augmentedCourse). This could be performed by a method like this
Then I would make a step outwards and would create a method that, starting from a courseId, returns an Observable which emits an augmentedCourse, i.e. an object with all the course and the field details.
Now let's do a further step outwards, and create a method that, starting from a session, returns an object with all properties of the session plus the properties of the augmentedCourse the session refers to. Let's call this object augmentedSession.
Now one more step outwards. We want to fetch an augmentedSession starting from a courseSessionId.
So, what did we achieve so far? We are able to create an Observable which emits an augmentedSession starting from a courseSessionId.
At the outermost level though we have a list of applications, each of which contains a courseSessionId. Different applications can share the same course and so have the same courseSessionId. Therefore it makes sense to fetch all applications, create a list of unique courseSessionIds, use such list to fetch all the courses and then assign to each application its course. In this way we avoid querying the back end more than once for the same course.
This can be acheived like this
With a similar style you should be able to add also dates details to your sessions. Also in this case we start with innermost operation, which is to retrieve an array of tasks given an array of taskIds via
his.tasksService.get
.The code would look like this
Moving one step outward, we can augment each date of an array of dates with tasks that belong to that date like this
Now we can augment a session with its dates like this
We can now complete the
fetchAugmentedSession
method, adding also the part that augment the session with dates info like thisIn this way you have split your logic in smaller chunks, which are easier to test and, hopefully, easier to read.
I did not have any playground to test the code, so it is very well possible that there are typos in it. I hope though that the logic is clear enough.