Home > Net >  Angular Firestore - Display contents of sub-document array
Angular Firestore - Display contents of sub-document array

Time:11-02

This question has a long set-up. In the end, I'm asking for syntax coaching, and you'll see that question if you scroll to end of the set-up.

I have a Firestore database that has a fields that contain arrays. Here's an example:

enter image description here

The document itself has other fields beyond the ingredients element. Here's a JSON representation of the Congo Split recipe you see here:

"congosplit": {
  "ingredients": {
    "a": {
      "ingredientAmounts": "2 Pump, 3 Pumps, 4 Pumps",
      "ingredientName": "Banana Syrup",
      "ingredientSizes": "Small, Medium, Large"
    },
    "b": {
      "ingredientAmounts": "2 Pump, 3 Pumps, 4 Pumps",
      "ingredientName": "White Chocolate",
      "ingredientSizes": "Small, Medium, Large"
    },
    "d": {
      "ingredientAmounts": "10 oz, 12 oz, 16 oz",
      "ingredientName": "Steamed Milk and Foam",
      "ingredientSizes": "Small, Medium, Large"
    }
  },
  "instructions": {
    "a": {
      "text": "Free pour milk into cup.  Top with 1/4 inch foam.  Make a circle with chocolate drizzle and use a thermometer to spread the chocolate into a flower pattern.",
      "type": "text"
    }
  },
  "name": "Congo Split",
  "temperature": "hot"
},

As one might infer from the structure above, both the ingredients and the instructions fields may contain more than one element... designed with a, b, and c in the sample below.

I have followed the sample code snippets for using Angular Firestore:

in my recipes.service.ts file, I have the following method that provides a real-time-updating copy of the data in Firestore:

  getRecipesFirestore() {
    this.recipesCollection = this.afs.collection('mission-cafe');
    this.recipesData = this.recipesCollection.valueChanges();
    return this.recipesData;
  }

in the home.ts file I call that method, returning the recipesData:

  ngOnInit() {

   [...]

    this.recipesData = this.recipesService.getRecipesFirestore();
  }

And the good news is I can easily show the content of most of this data set using an *ngFor in the template file:

  <ion-card *ngFor="let item of recipesData | async">
     <ion-title>{{item.name}}</ion-title>
  </ion-card>

This works for all of the keys at the top level of the document field. But when I try to display the sub-elements of ingredients or instructions I get a [object] instead of the content. It is not totally surprising. Here's an example of what doesn't work.

  <ion-card *ngFor="let item of recipesData | async">
     <ion-title>{{item.name}}</ion-title>

     <div *ngFor="let ingred of item>                     < I was hoping to be able
         <ion-text>{{ingred.ingredientName}}</ion-text    < to iterate through elements
     </div>                                               <  of the ingredient array

  </ion-card>

So the question:
I cannot figure out the syntax to get to the content of the maps inside the recipe field.

I can imagine parsing the JSON (I did that when I was using the Real Time Database), but I was hoping to take advantage of the simple syntax made simple with Firestore as implied by the template file above.

Any syntax coaches out there?

CodePudding user response:

At the time of writing, the only libraries/SDKs that allow listing the sub-collections of a document are the ones that are used in "trusted server environments" like the ones for Node.js, Java, PHP, etc. See enter image description here

So the remaining question is how to make all of this data show up in the app. Here's conceptually what you want to have happen.

enter image description here

This was the approach:

In ngOnInit we get the collection, and subscribe to the recipesInfo

ngOnInit() {

this.recipesCollection = this.afs.collection('mission-cafe');
this.recipesInfo = this.recipesCollection.snapshotChanges();

this.recipesInfo.subscribe((actionArray) => {
  this.recipesElements = actionArray.map((item) => ({
    id: item.payload.doc.id,
    ...item.payload.doc.data(),
    expanded: false
  }));
});

}

To explain this for newbies... recipesElements is the array over which the template file (the HTML file) will iterate. So we load that up with the IDs of the recipe documents and all of the rest of the recipe documents. That's what the spread operator ... does. It stands in for all of the rest of that data. Then I add one more element expanded that I use later to decide whether the recipe should be expanded (open) or collapsed in an accordion display of ion-cards.

Then in the template file, I have this:

<ion-card
  (click)="expandItem($event, false, item)"  <--- use this to toggle open/closed on accordion display
  *ngFor="let item of recipesElements">
  <ion-card-header>
      <ion-card-title>{{item.name}}
  </ion-card-header>
  <ion-card-content>
    <app-expandable expandHeight="6500px" [expanded]="item.expanded">

         [[[[insert formatting here for the display]]]]
         [[[[my formatting includes the individual fields]]]]

              item.notes
              item.image
              item.status
              and so on.

         [[[[ for instructions, I had an secondary *ngFor like this ]]]]

              <ion-item *ngFor="let ingred of item.ingredients">
         
         [[[[ and then refer to the individual ingredients as ]]]]

              <ion-grid>
                   <ion-row>
                        <ion-col> {{ingred.ingredientName}} </ion-col>
                        <ion-col> {{ingred.ingredientSizes}} </ion-col>
                        <ion-col> {{ingred.ingredientAmounts}} </ion-col>
                   </ion-row>
              <ion-grid>
          </ion-item>
     <ion-card>

So it is fairly straight-forward. I just needed to understand the structure of a Firestore collection and then create an array from it over which to iterate.

  • Related