0

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:

  • Snake: White
  • Lizard: Black

Mammals:

  • Monkey: Brown
  • Cat: White

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.

ACerts
  • 308
  • 1
  • 6
  • 22
Joel
  • 974
  • 2
  • 10
  • 18

3 Answers3

2

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

Joel
  • 974
  • 2
  • 10
  • 18
  • The use of "map" from latest angularfire lib has been changed.... need to add "pipe" like this example: this.afs.collection('Category').snapshotChanges().pipe(map(arr => { ... })) – rickdroio Aug 15 '18 at 16:21
1

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.

Christian Benseler
  • 7,907
  • 8
  • 40
  • 71
  • This is not a valid answer since you are assuming he knows what is inside of a collection. He mentioned " I always have just one known subcollection called animalsubcollection" – Patricio Vargas May 18 '18 at 18:06
  • 1
    I also think this normalisation is not the best approach. `animals_categories` needs only if a animal belongs to more then one category. Thats not true. Anyway thanks for reply. – Joel May 18 '18 at 18:15
  • 2
    IMO this is a good approach, because you have separate nodes for each kind of information. One for the categories, one for the animals and another for the relation. An animal being part of only one category is not a problem. With this structure, it's possible to easily fetch each type of data and you don't have nested objects (which sometimes can be a sort of "nesting hell"), there is only one level of data in each node. – Christian Benseler May 18 '18 at 18:21
  • 1
    I agree with @ChristianBenseler, your data needs some clean up – Patricio Vargas May 18 '18 at 18:25
  • 1
    This is close, but I would merge properties of `categories` and `animals_categories` since they are synonyms. –  May 18 '18 at 20:47
0

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;
      }, {});
}
Patricio Vargas
  • 5,236
  • 11
  • 49
  • 100