Reputation: 154
I have an app where questions are shown to users. Drafts for the questions are loaded from a SharePoint list. Each draft contains a key which is used to load proper responses to the question from another SharePoint list. Here's how I currently implemented it:
interface QuestionDraft {
title: string;
responseKey: string;
}
interface Question {
title: string;
responses: string[];
}
const drafts: QuestionDraft[] = [];
const questions: Question[] = [];
// stub
private getDrafts(): Observable<QuestionDraft> {
return from(drafts);
}
// stub
private getResponses(key: string): Observable<string> {
return of(key, key, key);
}
main(): void {
getDrafts().subscribe(
data => {
const res: string[] = [];
getResponses(data.responseKey).subscribe(
d => res.push(d),
error => console.error(error),
() => questions.push({
title: data.title,
responses: res
})
);
}, error => console.error(error),
() => console.log(questions)
);
}
This solution works fine, but I think the code in main()
looks messy. Is there an easier way to do the same thing, for example using mergeMap
or something similar?
Upvotes: 0
Views: 2754
Reputation: 14099
You can use mergeMap
to map to a new Observable and toArray
to collect the emitted values in an array. Use catchError
to handle errors in your streams and map to an alternative Observable on errors.
This code will work just like your code with the emitted questions array containing all questions up until getDrafts
throws an error and exluding questions for which getResponses
threw an error.
getDrafts().pipe(
mergeMap(draft => getResponses(draft.responseKey).pipe(
toArray(),
map(responses => ({ title: draft.title, responses } as Question)),
catchError(error => { console.error(error); return EMPTY; })
)),
catchError(error => { console.error(error); return EMPTY; }),
toArray()
).subscribe(qs => { console.log(qs); questions = qs; })
Keep in mind that the questions
in the final array will not necessarily be in the same order as the drafts
coming in. The order depends on how fast a getResponses
Observable completes for a specific draft
. (This is the same behaviour as your current code)
To ensure that the questions
will be in the same order as the drafts
you can use concatMap
instead of mergeMap
. But this might slow down the overall execution of the task as the responses for the next draft
will only be fetched after the responses for the previous draft
completed.
Upvotes: 1
Reputation: 429
If you use RxJS version 6 you must use pipe() method and turned flatMap into mergeMap.
in rxjs 6, example of @emcee22 will be look:
this.getDrafts()
.pipe(
.mergeMap(function(x){return functionReturningObservableOrPromise(x)}),
.mergeMap(...ad infinitum)
).subscribe(...final processing)
Upvotes: 0
Reputation: 1889
You can try and use flatMap
to make it cleaner.
RxJs Observables nested subscriptions?
this.getDrafts()
.flatMap(function(x){return functionReturningObservableOrPromise(x)})
.flatMap(...ad infinitum)
.subscribe(...final processing)
Upvotes: 0