Seims
Seims

Reputation: 154

RxJS: Mapping object keys to observables

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

Answers (3)

frido
frido

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

Kamil Augustyniak
Kamil Augustyniak

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

emcee22
emcee22

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

Related Questions