Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I join two Firestore queries using rxfire and rxjs (OR query)

The goal is simple: to join two firestore queries utilizing rxjs, rxfire, and the rnfirebase react native library.

I've read multiple tutorials 1, 2 on joining queries, but they all fail with different errors.

//Simple test for collectionData
import { collectionData } from 'rxfire/firestore';

this.myQuery = this.props.docRef.collection(`messages`).where('read', 'array-contains', this.props.me.uid)
collectionData(this.myQuery, 'id').subscribe(docs => console.log(docs))
//Fails with error: this._next is not a function.

Alternatively,

this.publicQuery = this.props.docRef.collection('messages').where('public', '==', true) 
this.myQuery = this.props.docRef.collection(`messages`).where('read', 'array-contains', this.props.me.uid)
const myQuery$ = new Rx.Subject();
const publicQuery$ = new Rx.Subject();
this.myQuery.onSnapshot((querySnapshot) => {
    myQuery$.next(querySnapshot.docs.map(d => d.data()  ));
});
this.publicQuery.onSnapshot((querySnapshot) => {
    publicQuery$.next(querySnapshot.docs.map(d => d.data()  ));
});
const orQuery$ = combineLatest(this.myQuery, this.publicQuery).switchMap((docs) => {
    var [one, two] = docs;
    var combined = one.concat(two);
    return Rx.Observable.of(combined);
})
orQuery$.subscribe((result) => {
    console.log('>>>> ', result)
})
//TypeError: undefined is not a function (near ...switchMap)

How can I successfully join two firestore queries (OR)?

like image 500
TIMEX Avatar asked Dec 14 '22 13:12

TIMEX


1 Answers

You're already very close to the solution. Let's go through the issues step-by-step.

First, it's not necessary to create a Subject just to transform your result from onSnapshot. Instead of this:

this.myQuery.onSnapshot((querySnapshot) => {
    myQuery$.next(querySnapshot.docs.map(d => d.data()))
});

We can achieve the same using 'pipeable transformation operators':

const myQuery$ = this.myQuery.onSnapshot.pipe(
    map(querySnapshot => querySnapshot.docs.map(d => d.data()))
);

The same goes for the other query:

const publicQuery$ = this.publicQuery.onSnapshot.pipe(
    map(querySnapshot => querySnapshot.docs.map(d => d.data())
);

Second, to join those two queries, combineLatest is indeed the correct creation function.

However, your error might result from you using a newer RxJS version, that doesn't support 'fluent' operators (officially called "patch operators") anymore. They have been replaced by 'pipeable operators' from RxJS 6 onwards. As an example, myObs$.map(...) has become myObs$.pipe(map(...)). The tutorials probably use an older version of RxJS where the first is still possible.

Also, it shouldn't be necessary to use switchMap if the inner Observable is just an of operator. It is sufficient in this case to use the map operator, which will behave equally.

Using the new RxJS 6+ syntax together with map, the combination will look like this:

const orQuery$ = combineLatest(myQuery$, publicQuery$).pipe(
    map(([one, two]) => one.concat(two))
)

The rest of your code should be correct.

Side Note: Keep in mind that the equivalent of your code in SQL is UNION (not JOIN). In order to JOIN programatically, you'd need to combine each object of result set A with each object of result set B and create a joined object for each pair. Such a function for a keyless OUTER JOIN would look like this (placed in your map pipe):

one.map(a => 
   two.map(b => Object.assign({}, a, b)))
.reduce((p, c) => p.concat(c), [])

If you want to have a UNION with no duplicate objects, concat only those items from two that have no matching primary key in list one. This would be your mapping function:

one.concat(two.filter(twoItem => !one.some(oneItem => oneItem.id == twoItem.id)))

DEMO: A complete, working demo with the above code and a simulated FireStore can be found here:

https://stackblitz.com/edit/rxjs-mefynu?devtoolsheight=60

like image 150
ggradnig Avatar answered Apr 05 '23 03:04

ggradnig