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}))
)
))
})
)
}