1

I am trying to query a sub-collection if exists while querying for document from collection. My first query returns/maps data properly but when I try to query the subcollection it will be stored as observable within the Observable<data>. Below is what I have/tried so far.

component.ts

availableCategoriesCollection: AngularFirestoreCollection<Category>;
availableCategories$: Observable<CategoryId[]>;
lstCategories: Observable<any>;

this.availableCategoriesCollection = this.httpDataService.getAllCategories();
this.availableCategories$ = this.availableCategoriesCollection.snapshotChanges().map(data => {
  return data.map(record => {
    const rec = record.payload.doc.data() as Category;
    const cId = record.payload.doc.id;
    return {cId, ...rec};
  });
});

this.lstCategories = this.availableCategories$.map(data => {
  return data.map((rec: CategoryId) => {
    //if a category has subcategory then query the document again for subcollection
    if (rec.hasSubCat) {
      return this.httpDataService.getSubCategory(rec.cId).snapshotChanges().concatMap(d => {
        return d.map(r => {
          const arr: any = {};
          arr.id = r.payload.doc.id;
          arr.itemName = (r.payload.doc.data() as Category).categoryName;
          arr.category = rec.categoryName;
          return arr;
        });
      });
    }else {
      const arr: any = {};
      arr.id = rec.id;
      arr.itemName = rec.categoryName;
      arr.category = 'All';
      return arr;
    }
  });
});

When I look into the lstCategories value, the documents that have subcollection will be returned as Observable and the ones which don't have subcollection returns proper data with id,itemName and category. Something like below:

(9) [{…}, Observable, Observable, {…}, {…}, {…}, {…}, {…}, Observable]

How can I properly subscribe to the sub-query? What am I missing here?

1 Answer 1

2

So your problem is that the maps callback sometimes returns a plain object and sometimes returns an Observable. I guess you still want to emit an array of objects (including those inside the inner Observables) instead of emitting items one by one.

I think the easiest way to do this is using forkJoin and always returning an Observable (even when the result could be a plain object):

this.lstCategories = this.availableCategories$.mergeMap(data => {
  const observables = data.map((rec: CategoryId) => {
    if (rec.hasSubCat) {
      return this.httpDataService... // Observable
    } else {
      const arr: any = {};
      ...
      return Observable.of(arr); // Observable
    }
  }

  return Observable.forkJoin(observables);
});

Also notice that I had to use this.availableCategories$.mergeMap() because mergeMap will subscribe to the forkJoin Observable and emit its result.

Sign up to request clarification or add additional context in comments.

3 Comments

I get observable_1.Observable.of is not a function error in console for the statement return Observable.of(arr); under else. :(
Ok, I got that issue right, since I was importing Observable from rxjs/observable wherein I had to import from rxjs/Observable. Now the issue is the data will not get displayed when I have async pipe in my view. If I subscribe to lstCategories in component, it never gets invoked. Any idea why is it so?
This returns data as - (9) [ScalarObservable, Observable, Observable, ScalarObservable, ScalarObservable, ScalarObservable, ScalarObservable, ScalarObservable, Observable] i.e. 3 Observables and remaining 6 ScalarObservable. But this data is not subscribable and doesn't work with async..

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.