I've been trying to combine multiple async calls for a while now, and everytime I get close, I get stuck at a foreach loop I'm trying to solve.
At the moment I pass an array of categories to a function, which will return an observable containing an array of all the questions (of all the categories combined).
I was thinking of
getQuestions(categories: Category[]): Observable<Question[]> {
let q: Question[];
categories.forEach(c => {
this.cs
.getQuestionsCategory(c.id)
.pipe(map(questions => q.push(...questions)));
//return q somehow?
});
}
I would then be able to use it like this:
let result: Result;
let res: [Category[], Account, Answer[]];
return this.getResultByRouteParamId(route).pipe(
tap(resu => (result = resu)),
switchMap((result: Result) =>
this.forkJoinQuizCategoriesAccountAnswers(result)
),
tap(results => (res = results)),
switchMap(x => this.getQuestions(res[0])),
map(questions => {
// Create objects of questions, answers and category and add them to a QuestionAnswer object
// Return new UserResult containing the user and a list of QuestionAnswer objects
return new UserResult(null, null);
})
);
This is as close as I could get to someone's recommendation on my previous question. I wanted to add this to my original question, but I feel this wouldn't be the right thing to do since I'm no longer struggling with nested observables, but rather with looping over them.
EDIT
Tried something else, but I doubt this is the right way to do it
getQuestions(categories: Category[]): Observable<Question[]> {
let ques = categories.map(c =>
this.cs.getQuestionsCategory(this.auth.token, c.id)
);
return merge(...ques);
}
Firstly, I like the idea of putting this into a dedicated function, so that you can separate the interface from the implementation.
The problem you want to solve should be doable in the pipe, it's just a case of working out what's going on.
Category.Question.I would approach this by:
forkJoin the observable array. By itself this will return a 2-dimensional arraymap the 2-dimensional array to a flat arraygetQuestions(categories: Category[]): Observable<Question[]> {
// firstly, start out with an array of observable arrays
const observables: Observable<Question[]>[] = categories.map(cat =>
this.cs.getQuestionsCategory(cat.id));
// run all observables in parallel with forkJoin
return forkJoin(
observables
).pipe(
// now map the array of arrays to a flattened array
map(questions => this.flat(questions))
);
}
// alternative to arr.flat()
private flat<T>(arr: T[][]): T[] {
return arr.reduce((acc, val) => acc.concat(val), []);
}
You should combine those streams with forkJoin or combineLatest:
getQuestions(categories: Category[]): Observable<Question[]> {
return forkJoin(
categories.map(category => this.cd.getQuestionsCategory(category.id))
).pipe(tap(questions => { console.log(questions) })); // Here you can change the data structure of questions instead of just logging them
}