Inner join collections based on locations with geofireX

196 Views Asked by At

I want to fetch and join multiple collections based on user's location with geofirex:

Here's my code when I get users docs based on location : PS (This code returns only user's docs based on location)

getAround() {
    const center = geo.point(this.lat, this.lng);
    const field = 'location';
    this.points = this.radius.pipe(
      switchMap(r => {
        return this.geo.query(locations).within(center, r, field, { log: true });
      })
    );
}

How I inner join collections with Rxjs :

 getAll() {
      const allPosts = this.afStore.collection<Service>('services')
        .valueChanges()
        .pipe(
          switchMap(services => {
            const res = services.map((service: Service) => {
              return this.afStore.doc<UserModel>(`users/${service.userId}`)
              .valueChanges()
              .pipe(
                map(user => Object.assign(service, { user }))
              );
            });
            return combineLatest([...res]); })
        )
         .pipe(
          switchMap(servuser => {
            const ress = servuser.map((service: Service) => {
              return this.afStore.doc<Category>(`categorys/${service.iconId}`)
              .valueChanges()
              .pipe(
                map(category => Object.assign(service, { category }))
              );
            });
            return combineLatest([... ress]);
          })
        );
      return allPosts;
    }

The problem is I can't mix these codes together so I can get my data joined and based on user's location,

I get this error:

(alias) interface Service 
import Service 
Argument of type '(service: Service) => Observable<Service & { user: UserModel; }>' is not assignable to parameter of type '(value: GeoQueryDocument, index: number, array: GeoQueryOocument[]) => Observable<Service & { user: UserModel; }>'. Types of parameters 'service' and 'value' are incompatible. 

Type 'GeoQuerpocument' is missing the following properties from type 'Service': id, cost, dateCreated, discount, and 6 more. 

3

There are 3 best solutions below

1
On BEST ANSWER

Adding this fixed it and now my code works!

.pipe(
          switchMap(services => {
            const res = services.map((fromservices) => {
              return this.afStore.doc<UserModel>(`users/${fromservices['userId']}`)
              .valueChanges()
              .pipe(
                map(user => Object.assign(fromservices, { user }))
              );
            });
            return combineLatest([...res]); })
        )

And just having this warning which is okay I guess? :

object access via string literals is disallowed (no-string-literal)tslint(1)
0
On

You might want to take a look into this question. They explain step by step a similar scenario to yours.

They also shared a DEMO: A complete, working demo with the above code and a simulated FireStore.

Additionally to that, I found this about Join Collections in Firestore that might be of your interest.

I suggest to take a look into the question aforementinoed. Anyway, I will add here some remarkable things of that:

  • You can use pipeable transformation operators:
const myQuery$ = this.myQuery.onSnapshot.pipe(
    map(querySnapshot => querySnapshot.docs.map(d => d.data()))
);
  • To join those two queries, combineLatest is 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))
)

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)))
1
On

The error says that services is from the value-index-array type, while you implicetely expect a Services array.

    .pipe(
      switchMap(services => { // <-- expects a Service array (but appreantly it's not)
        const res = services.map((service: Service) => { // <-- because of this
          return this.afStore.doc<UserModel>(`users/${service.userId}`)
          .valueChanges()
          .pipe(
            map(user => Object.assign(service, { user }))
          );
        });
        return combineLatest([...res]); })
    )