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?
CodePudding user response:
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;
})
);
}
}
CodePudding user response:
If your hero and skills are loaded once, you should consider using promises instead of observables. If you cannot change it to the core, you can convert an observable to a promise with .toPromise()
. Then you can use Promise.all()
.
getHeroFromData(data:JsonArray): Promise<Hero> {
let hero = new Hero().fromJSON(data);
if (hero.idSkill1 && hero.idSkill2 && hero.idRarity) {
const promise 1 = this.skillService.getSkill(hero.idSkill1).toPromise().then(skill => hero.skill1 = skill);
const promise2 = this.skillService.getSkill(hero.idSkill2).subscribe(skill => hero.skill2 = skill);
const promise3 = this.rarityService.getRarity(hero.idRarity).subscribe(rarity => hero.rarity = rarity);
return Promise.all([promise1, promise2, promise3]).then(() => hero);
} else {
return Promise.resolve(hero);
}
}
Then you must use then
when calling this method. Because hero is build in a async way, you must treat the return in a async way. Use then().