简体   繁体   中英

Get merged observable from id with rxjs Angular Firestore

I've a Firestore DB with persons and pets, pets has owner id.

// Collection / DocumentID: {data}

persons / 'person1': { name: 'Romea' }
persons / 'person2': { name: 'Julieto' }
pets / 'pet1': { name: 'Forky', ownerID: 'person1' }
pets / 'pet2': { name: 'Rasky', ownerID: 'person1' }
pets / 'pet3': { name: 'Tursy', ownerID: 'person2' }

so, Romea owns Forky and Rasky when Julieto owns Tursky.

I'm using @angular/fire and I want to use a recent version of rxjs (6.5.x) to get a list of users observables with their pets each, see following structure:

[
   0: { 
         name: 'Romea',
         pets: [ 0: { name: 'Forky', ownerID: 'person1' }, 1: { name: 'Rasky', ownerID: 'person1' } ]
      },
   1: {
         name: 'Julieto',
         pets: [ 0: { name: 'Tursky', ownerID: 'person2' } ]
      }
]

After all, I want to call person.pets , like that:

this.personService.getPersonsWithPets().subscribe(persons => {
   console.log(persons[0].pets)
   // result: [{ name: 'Forky', ownerID: 'person1' }, { name: 'Rasky', ownerID: 'person1' }]

   persons.forEach(person => person.pets.forEach(pet => console.log(pet.name)))
   // result: 'Forky', 'Rasky', 'Tursky'
})

SOLUTION

According to @bryan60 's solution, this is what I've done (rxjs plus a class that receives in constructor both person and pets):

service:

private personsCollection: AngularFirestoreCollection<Person> = this.db.collection<Person>('persons')

private _getPersonsWithoutPets(): Observable<Person[]> {
   return this.personsCollection.valueChanges()
}

getPersons(): Observable<(Person)[]> {
   return this._getPersonsWithoutPets().pipe(
      switchMap(persons => {
         return combineLatest(persons.map(person => this.getPersonPets(person.id).pipe(
            map(pets => new Person(person, pets))
         )))
      })
   )
}

getPersonPets(personId: string): Observable<Pet[]> {
   return this.db.collection<Pet>('pets', ref => ref.where('personId', '==', personId)).valueChanges()
}

models:

export class Person {
   id?: string
   name: string

   public constructor(person: Person = null, private pets: Pet[] = null) {
       Object.assign(this, person)
       this.pets = pets
   }

   model?() {
      return {
         id: this.id,
         name: this.name
      }
   }
}

export class Pet {
   id?: string
   name: string

   constructor(pet: Pet = null) {
      Object.assign(this, pet)
   }

   model?() {
      return {
         id: this.id,
         name: this.name
      }
   }
}

component:

personsWithPets: Person[] = []

constructor(private personService: PersonService) {
   this.getPersons()
}

getPersons() {
   this.personService.getPersons().subscribe(p => this.personsWithPets = p)
}

Solution for situations where persons has multiple references (pets, cars):

getPersonsWithReferences(): Observable<Person[]> {
  return this.getPersons().pipe(switchMap(persons =>
    combineLatest(persons.map(person =>
      combineLatest(this.getPersonPets(person.id), this.getPersonCars(person.id))
        .pipe(map(([pets, cars]) => new Environment(person, pets, cars)))))))
}

this is a switchMap use case.

supposing you had functions getPersons() and getPersonsPets(ownerID) , which do what their names suggest, do it like this:

getPersonsWithPets() {
  return this.getPersons().pipe(
    switchMap(persons => {
      return combineLatest(persons.map(person => this.getPersonsPets(person.id).pipe(
        map(pets => Object.assign(person, {pets}))
      )))
    })
  )
}

first get persons, switchMap into combineLatest of persons mapped into a stream of their pets and assign the pets to the person, and return a list of the people with their pets.

with more assignments:

getPersonsWithPets() {
  return this.getPersons().pipe(
    switchMap(persons => {
      return combineLatest(persons.map(person => 
        combineLatest(
          this.getPersonsPets(person.id),
          this.getPersonsCars(person.id),
          this.getPersonsHouses(person.id)
        ).pipe(
          map(([pets, cars, houses]) => Object.assign(person, {pets, cars, houses}))
        )
      ))
    })
  )
}

You can fetch your both collections using forkJoin , where you get an array of your data, both all persons and pets . Then you can modify for your data, and combine these data in the manner you like, ie all pets with owner x should be added to the object of the person x . If you are looking for watching real time updates from db, I suggest using combineLatest instead.

Here is a sample, but you need to apply it to your usecase with angularfire and use the doc refs accordingly:)

allData = [];

// added an id property here for this sample, you will use the doc ref
persons = [{ id: 1, name: "Romea" }, { id: 2, name: "Julieto" }];
pets = [
  { name: "Forky", ownerID: 1 },
  { name: "Rasky", ownerID: 1 },
  { name: "Tursy", ownerID: 2 }
];

ngOnInit() {
  // in your usecase, combine your two queries
  forkJoin(of(this.persons).pipe( /** here map accordingly **/), 
           of(this.pets).pipe(/** here map accordingly **/)).pipe(
      // DON'T USE ANY, type your data!
      map((data: [any, any]) => {
        const persons = data[0];
        const pets = data[1];
        // here combine the data
        return persons.map(person => {
          const personPets = pets.filter(p => p.ownerID === person.id);
          return { ...person, pets: [...personPets] };
        });
      })
    )
    .subscribe(d => this.allData = d);
}

here map accordingly just means that in your code you need to map the values based on how you are making the queries, assumingly using snapshotChanges .

STACKBLITZ of the above code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM