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)?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With