Joel
Joel

Reputation: 1064

Retrieve all documents from just one known subcollection

I have this Firestore structure:

Category: {
    document1a: {
        name : "reptiles"
        animalsubcollection: {
            Document1b: {
                name: "snake"
                color: "white"
            }
            Document2b: {
                name: "lizard"
                color: "black"
            }
    document2a: {
        name : "mammals"
        animalsubcollection: {
            Document1b: {
                name: "monkey"
                color: "brown"
            }
            Document2b: {
                name: "cat"
                color: "white"
            }

I need to list that information in a single page, classified by category like this:

Reptiles:

Mammals:

Like the example above I always have just one known subcollection called animalsubcollection.

How I can achieve this using AngularFire2 in Angular 5?

If it's not the best firestore model for this kind of information, I can change it if necessary. I accept others suggestions to achieve the same result.

Upvotes: 0

Views: 1028

Answers (3)

Joel
Joel

Reputation: 1064

Thanks all for the suggestions, but the initial proposal wasn't normalize the original database model, but find a simple solution using the original model.

I known that sometime, is more easy just simple create 2 ou 3 new normalized collections and solve the problem. In the other hand, if there is a possibility to create a subcollection it needs to exist a way to query his data in the Angular em RxJS way.

So, after 2 ou 3 days of research I found a way to do this:

//necessary imports
import { Observable } from 'rxjs/observable';
import { combineLatest } from 'rxjs/observable/combineLatest';
import 'rxjs/add/operator/mergeMap';

// the observable for animals category collection
this.animals = this.afs
  .collection('Category')
  .snapshotChanges()
  .map(arr => {
    return arr.map(snap => {
      const categ = { id: snap.payload.doc.id, ...snap.payload.doc.data() };

      // the animalsubcollection, using the previous value as parameter
      return this.afs
        .collection(`Category/${categ.id}/animalsubcollection`)
        .valueChanges()
        .map(animalsub => {
          return { ...categ, animalSubs: animalsub };
        });
    });
  })
  .mergeMap(result => combineLatest(result));
  //thats the trick. Combine 2 observables into a new one.

In the view:

<ion-card *ngFor="let animal of animals | async">
<p *ngFor="let as of animal.animalSubs">
     {{as.name}} - {{as.color}}
</p>

I hope it helps someone

Upvotes: 2

Patricio Vargas
Patricio Vargas

Reputation: 5522

Assuming you have a valid Array of JSON (the one you posted isn't valid)You can accomplish this in a very easy way using Angular.

You can easily archive this using underscore in your angular app.

how to use underscore.js library in angular 2

groupedSeriesNames = []
groupedSeries = []



categories =  Category: {
        document1a: {
            name : "reptiles"
            animalsubcollection: {
                Document1b: {
                    name: "snake"
                    color: "white"
                }
                Document2b: {
                    name: "lizard"
                    color: "black"
                }
        document2a: {
            name : "mammals"
            animalsubcollection: {
                Document1b: {
                    name: "monkey"
                    color: "brown"
                }
                Document2b: {
                    name: "cat"
                    color: "white"
                }
           .
           .
           .

Example of a better Datastructure:

{
        type: "reptiles",       
        name: "snake",
        color: "white"


    },
    {
        type: "reptiles",       
        name: "snake",
        color: "white"
    },
    {
        type: "mammals",        
        name: "snake",
        color: "white"
    }
}

OR

{
        name: "reptiles",
        animalsubcollection: {
            Document1b: {
                name: "snake",
                color: "white"
            }
        }
    },
    {
        name: "mammals",
        animalsubcollection: {
            Document1b: {
                name: "leion",
                color: "white"
            }
        }
    },
    {
        name: "mammals",
        animalsubcollection: {
            Document1b: {
                name: "papa",
                color: "white"
            }
        }
    }
this.groupedTypes = _.groupBy(this.categories, category=>category.name);
this.groupedSeriesNames = Object.keys(this.groupedSeries)

The certificate.serie will become they key, you can change the certificate.serie to any other property like iden or whatever you need

your html

<ul class="cert-result">
    <li *ngFor="let key of groupedSeriesNames">
      <table *ngFor="let category of groupedSeries[key]">
        <tr>
          <th>Name</th>
          <th>Color</th>
        </tr>
        <tr>
          <td>{{category.color}}</td>
          <td>{{category.name}}</td>
        </tr>
      </table>
    </li>
  </ul>

Code without using underscore (assuming json is correct structured)

function formatedCerts() {
      return categories.reduce((prev, now) => {
        if (!prev[now.name]) {
          prev[now.name] = [];
        }

        prev[now.name].push(now);
        return prev;
      }, {});
}

Upvotes: 0

Christian Benseler
Christian Benseler

Reputation: 8065

I would suggest you to nornalize your data structure. You can follow this pattern: https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape

In one node of your data structure, store all the "animals" you have. In other, store all the "animal categories". In other, store the relation between them, associating for each category the animal 'key'.

{
    animals: {
        snake: { color: 'white '},
        lizard: { color: 'black' },
        monkey: { color: 'brown '}
    },
    categories: {
        reptiles: { description: 'something here about reptiles'},
        mammals:  { description: 'something here about mammals'}
    },
    animals_categories: {
        reptiles: ['snake', 'lizard'],
        mammals: ['monkey']
    }

}

So you will be able to query the categories, then with the animals' keys for each category you will be able to fetch the animal details. Although the link I provided is from Redux' documentation, this pattern is not bound to Redux. This is a common usage in this kind of data structures, based on documents.

Upvotes: 1

Related Questions