I have two collections: persons and pets. Every pet has personId. My target is to get all persons and foreach of them to add his/her pets in single json. What I did so far is:
this.personService.getPersions().subscribe(persons => {
const personsWithPets = persons.flatMap(person => this.petService.getPetsByPersonId(person._id)
.subscribe(petsData => {
person.pets = petsData;
return person;
}, (err) => {
console.log(err);
}));
this.persons = personsWithPets; // This is fired before previous subscribe
}, (err) => {
console.log(err);
});
What I do wrong? Why this.persons = personsWithPets;
is fired before subscription finish?
I have two collections: persons and pets. Every pet has personId. My target is to get all persons and foreach of them to add his/her pets in single json. What I did so far is:
this.personService.getPersions().subscribe(persons => {
const personsWithPets = persons.flatMap(person => this.petService.getPetsByPersonId(person._id)
.subscribe(petsData => {
person.pets = petsData;
return person;
}, (err) => {
console.log(err);
}));
this.persons = personsWithPets; // This is fired before previous subscribe
}, (err) => {
console.log(err);
});
What I do wrong? Why this.persons = personsWithPets;
is fired before subscription finish?
-
1
because your
getPetsByPersonId
is probably asynchronus, so the code inside your call togetPetsByPersonId().subscribe
will likely not return a value until afterthis.persons = personsWithPets
. Thesubscribe
is called, just the code inside won't run until the service returns a value. It really has nothing to do with nested observables – BlackICE Commented Mar 13, 2019 at 11:08 - Any suggestions to make it works? – IntoTheDeep Commented Mar 13, 2019 at 11:13
4 Answers
Reset to default 2Updated added ments
Another one: stackblitz
this.service.getPersons().pipe(switchMap((per:any[])=>{
//create an array of observables
const obs=per.map(per=>this.service.getPet(per.id));
//call all of them in forkjoin
return forkJoin(obs).pipe(map(pets=>
//pets is an array, in pets[0] is the response of getPet(1),
//in pets[1] is the response of getPet(2)
pets.map((pet,i)=>{
return {
...per[i], //all the properties of the person
pets:pet //+ in pets an array with the pets of the person
}
})
))
})).subscribe(res=>this.res=res)
I have made an example for you
const { of } = rxjs;
const { map, switchMap, toArray, mergeMap } = rxjs.operators;
function getPeople() {
return of([{ id: 1, name: 'Amanda' }, { id: 2, name: 'Nancy' }]);
}
function getPetsByPersonId(id) {
switch(id) {
case 1:
return of(['Doggie']);
case 2:
return of(['Kitten']);
}
}
const getPets = (person) => {
return getPetsByPersonId(person.id).pipe(
map(pets => ({ ...person, pets }))
)
}
getPeople().pipe(
switchMap(people => people),
mergeMap(getPets),
toArray()
)
.subscribe(peopleWithPets => console.log(peopleWithPets));
<script src="https://unpkg./rxjs/bundles/rxjs.umd.min.js"></script>
Using subscribe inside subscribe is considered as a bad practice and may lead to problems as you described. I would suggest to use mergeMap bined with forkJoin operator:
this.personService.getPersions().pipe(mergeMap(persons => {
const requests = persons.map(person => this.petService.getPetsByPersonId(person._id));
return forkJoin(of(persons), ...requests);
}),
map(values => {
const persons = values[0];
const pets = values.slice(1);
// here you need to assign correct pet to correct person
})
).subscribe(personsWithPets => {
console.log(personsWithPets);
}, err => {
console.log(err);
});
Observables are asynchronous by nature. The subscribe call will be run in another thread while the rest of the instructions in the method will continue to run in the main thread. What you need to do is wait until the asynchronous action pletes before running the next one. You can do this by putting the isntructions inside of the subscription, or better yet use the onCompleted parameter which es after the (err) param, like so
this.personService.getPersons().subscribe(persons => {
const personsWithPets = persons.flatMap(person =>
this.petService.getPetsByPersonId(person._id)
.subscribe(petsData => {
person.pets = petsData;
return person;
},
(err) => {
console.log(err);
},
() => {
this.persons = personsWithPets;
}))
});
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745618911a4636401.html
评论列表(0条)