Is this the correct way to use rxjs MergeMap in angularfire2?

Viewed 606

I'm new in angular and rxjs, I'm trying to get three different values from firebase using angularfire2 and rxjs, and return it as a promise. After several research and trying i finally made it by using this code.

Here's the code (assuming afdb is angularfire-database):

getThreeValues() {
    return this.afdb.list('firebaseref1').valueChanges().pipe(
        map(data => {
            return data.length;
        }),

        mergeMap(first => {
            return this.afdb.object('firebaseref2').valueChanges().pipe(
                map(second => {
                    return ({first: first, second: second})
                }),
                take(1)
            );
        }),

        mergeMap(firstsecond => {
            return this.afdb.list('firebaseref3').valueChanges().pipe(
                map(third => {
                    return ({...firstsecond, third: third})
                }),
                take(1)
            );
        }),

        take(1))
        .toPromise();
}

The code works and I get promise which resolve into object that contain the three values I need.

The problem is that I'm not sure if this is the best way (as I'm not sure how this code works).

3 Answers

Since you are pretty sure you will only take the first value of your .valueChanges(), it will be much more efficient to wrap them into a .forkJoin() instead of .mergeMapping them all the way. With the help of destructuring from javascript, your code can look much simpler like this:

getThreeValues() {
    return forkJoin(
        this.afdb.list('firebaseref1').valueChanges().pipe(take(1)),
        this.afdb.object('firebaseref2').valueChanges().pipe(take(1)),
        this.afdb.object('firebaseref3').valueChanges().pipe(take(1))
    )
        .pipe(
            map(([first, second, third]) => ({
                first: first.length,
                second,
                third
            }))
        ).toPromise()
}

Well, you got really creative and solved the issue really nicely, but there are more effective ways of doing it. I don't know if you want to run the queries in parallel or in series, but since your code does not suggest that the three queries are dependent (the second taking data from first), I will provide a parallel solution, which should be faster to resolve.

Using combineLatest

import { combineLatest } from 'rxjs';

// returns promise of [value1, value2, value3]
return combineLatest(
    this.afdb.list('firebaseref1').valueChanges().pipe(
        map(data => {
            return data.length;
        }),
        take(1),
    ),
    this.afdb.object('firebaseref2').valueChanges().pipe(
        take(1),
    ),
    this.afdb.object('firebaseref3').valueChanges().pipe(
        take(1),
    ),
).toPromise();

Or even simpler (less code) solution (with a bit different possible result):

import { combineLatest } from 'rxjs';
return combineLatest(
    this.afdb.list('firebaseref1').valueChanges().pipe(
        map(data => {
            return data.length;
        }),
    ),
    this.afdb.object('firebaseref2').valueChanges(),
    this.afdb.object('firebaseref3').valueChanges(),
).pipe(
    take(1),
).toPromise();

You can achieve this using forkJoin as well.

firstCall(){
  return this.afdb.list('firebaseref1').valueChanges().pipe(
    map(data => {
      return data.length;
    });
}

secondCall(){
  return this.afdb.object('firebaseref2').valueChanges().pipe(
        mergeMap(second => second),
        take(1)
      );
}

thirdCall(){
  return this.afdb.object('firebaseref3').valueChanges().pipe(
        mergeMap(third => third),
        take(1)
      );
}

const observables = [];
observables.push(this.firstCall());
observables.push(this.secondCall());
observables.push(this.thirdCall());

return forkjoin(observables).toPromise();
Related