1
votes

7 observables imbriquées angulaires

J'ai deux collections: les personnes et les animaux. Chaque animal a personId. Mon objectif est d'obtenir toutes les personnes et pour chacune d'elles d'ajouter ses animaux de compagnie en un seul json. Ce que j'ai fait jusqu'à présent, c'est:

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

Qu'est-ce que je fais de mal? Pourquoi this.persons = personsWithPets; est déclenché avant la fin de l'abonnement?


2 commentaires

car votre getPetsByPersonId est probablement asynchrone, donc le code à l'intérieur de votre appel à getPetsByPersonId (). subscribe ne retournera probablement pas de valeur avant this.persons = personsWithPets < / code>. Le subscribe est appelé, seul le code à l'intérieur ne fonctionnera pas tant que le service n'aura pas renvoyé une valeur. Cela n'a vraiment rien à voir avec les observables imbriqués


Des suggestions pour que cela fonctionne?


4 Réponses :


1
votes

L'utilisation de subscribe dans subscribe est considérée comme une mauvaise pratique et peut entraîner des problèmes comme vous l'avez décrit. Je suggérerais d'utiliser mergeMap combiné avec l'opérateur forkJoin:

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


1 commentaires

Cette carte (valeurs => {me renvoie un tableau approprié d'animaux de compagnie, mais comment combiner personne et valeurs [nth]?



2
votes

J'ai fait un exemple pour vous

<script src="https://unpkg.com/rxjs/bundles/rxjs.umd.min.js"></script>
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));


3 commentaires

le tuyau n’existait pas sur le type Abonnement


Quelle pipe? getPetsByPersonId ou getPeople? Pourriez-vous s'il vous plaît poster le code de ces fonctions?


maintenant j'ai TypeError: this.getPetsByPersonId n'est pas une fonction



0
votes

Les observables sont asynchrones par nature. L'appel d'abonnement sera exécuté dans un autre thread tandis que le reste des instructions de la méthode continuera à s'exécuter dans le thread principal. Ce que vous devez faire est d'attendre la fin de l'action asynchrone avant d'exécuter la suivante. Vous pouvez le faire en mettant les isntructions à l'intérieur de l'abonnement, ou mieux encore utiliser le paramètre onCompleted qui vient après le paramètre (err), comme ceci

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


0 commentaires

2
votes

Commentaires mis à jour ajoutés

Un autre: 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)


0 commentaires