Search code examples
angularasynchronousrxjsangularfire2

How can I return an Observable with data of other Observables using his return value in the others


I have a project in which I want to return an Observable of Hero object. My heroes have multiple id properties to fetch data from other Observables as a Skill or a Rarity. My Observables come from the AngularFire library. Here is my Hero class what I have right now:

export class Hero extends Serializable {
  id?: string;
  name?: string;
  description?: string;
  idRarity?: string;
  rarity?: Rarity;
  stats: Stats;
  idSkill1?: string;
  idSkill2?: string;
  skill1?: Skill;
  skill2?: Skill;
  visual?: string;
  dateAdded?: Date;

  constructor() {
    super();
    this.stats = {};
    this.visual = "assets/images/placeholder.jpg";
  }
  ...

}

  // functions from the HeroService
  getHeroes(): Observable<Hero[]> {
    return this.db.collection<JsonArray>(HeroService.url)
      .valueChanges()
      .pipe(
        map(documents => {
          return documents.map(data=> {
            return this.getHeroFromData(data);
          });
        })
      );
  }

  getHero(id: string): Observable<Hero | undefined> {
    // Returns hero|undefined observable
    return this.getHeroDocument(id).valueChanges()
      .pipe(
        map(data => {
          return data ? this.getHeroFromData(data) : undefined
        })
      );
  }

  getHeroFromData(data:JsonArray) {
    let hero = new Hero().fromJSON(data);
    if (hero.idSkill1 && hero.idSkill2 && hero.idRarity) {
      this.skillService.getSkill(hero.idSkill1).subscribe(skill => hero.skill1 = skill);
      this.skillService.getSkill(hero.idSkill2).subscribe(skill => hero.skill2 = skill);
      this.rarityService.getRarity(hero.idRarity).subscribe(rarity => hero.rarity = rarity);
    }
    return hero;
  }

The issue I'm facing is that when my hero is returned, the data for my properties of rarity and skills are not set yet.

Is there a way to wait for all values to be received from the other Observables before returning the hero object?


Solution

  • RxJs 6.5+

    forkJoin will full fill your requirement here.

    know about mergeMap also

      // functions from the HeroService
      getHero(id: string): Observable<Hero | undefined> {
        // Returns hero|undefined observable
        return this.getHeroDocument(id)
          .valueChanges()
          .pipe(
            mergeMap((data) => {
              return data ? this.getHeroFromData(data) : of(undefined);
            })
          );
      }
    
      getHeroFromData(data: JsonArray): Observable<Hero> {
        let hero = new Hero().fromJSON(data);
        if (hero.idSkill1 && hero.idSkill2 && hero.idRarity) {
          forkJoin({
            skill1: this.skillService.getSkill(hero.idSkill1),
            skill2: this.skillService.getSkil1(hero.idSkill2),
            rarity: this.rarityService.getRarity(hero.idRarity),
          }).pipe(
            map((res) => {
              hero.skill1 = res.skill1;
              hero.skill2 = res.skill2;
              hero.rarity = res.rarity;
    
              return hero;
            })
          );
        }
      }