Search code examples
angulartypescriptfirebaserxjsangularfire2

Merging collection and sub collection observables in Angularfire2


I have integrated stripe into my firebase project (Firestore) which through web hooks adds and updates the products I have setup in Stripe to the firestore database. To display available products I have an observable which is subscribed in the template using async pipe. This all works fine, however, each product document in the products collection has a sub collection called 'prices' which contains the current and previous prices. I am trying to figure out how to return this additional data for each product in the main products observable.

The code to return just the products is:


export interface Item { name: string; id: string; }

  watchProducts () {

      const productsCollection: AngularFirestoreCollection<Item> =
      this._firestore
      .collection('products', ref => {
        return ref.where('active', '==', true);
      });

      const currentProducts: Observable<Item[]> = productsCollection.snapshotChanges().pipe(
        map(actions => actions
          .map(a => {
            const data = a.payload.doc.data();
            const id = a.payload.doc.id;
            console.log('a.payload');
            console.log(a.payload);
            return { id, ...data };
          }),
        ),
      );
      return currentProducts;
  }

The template code is:

            <button *ngFor="let product of products | async" ">
                {{product.name}} £/ month
            </button>

The location of the prices collection is:

this._firestore
.collection('products')
.doc({productId})
.collection('prices', ref => {
        return ref.where('active', '==', true);
      })

I would like to merge the results so in the async ngFor I could access the current price with active === true. I've looked at switchMap and mergeMap but can't seem to get anything to work properly. My knowledge is RxJs is somewhat limited.

Any help would be greatly appreciated.

Best A


Solution

  • You can create your own operator as Jeff introduced. But to simplify, you could manage it as following way:

    const products$ = this._firestore
      .collection('products')
      .snapshotChanges()
      .pipe(
        map((actions: any[]) => actions.map((a) => ({ ...a.payload.doc.data(), ...{ id: a.payload.doc.id } }))),
        switchMap((products: any[]) => {
          const pricesCols$ = products.map((p) =>
            this._firestore
              .collection(`products/${p.id}/prices`, (ref) => ref.where('active', '==', true))
              .valueChanges()
          );
    
          // passing the products value down the chain
          return combineLatest([of(products), combineLatest(pricesCols$.length ? pricesCols$ : [of([])])]);
        }),
        map(([products, pricesCols]) =>
          products.map((p, idx) => {
            p.prices = pricesCols[idx];
            return p;
          })
        )
      );