Reputation: 93
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?
Upvotes: 2
Views: 2737
Reputation: 888
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;
})
);
}
}
Upvotes: 2
Reputation: 93
After a few attempts, I manage to achieve it using zip operator
Here is what I have in the end.
getHeroes(): Observable<Hero[]> {
return this.db.collection<JsonArray>(HeroService.url)
.valueChanges()
.pipe(
mergeMap((docs) => {
return zip(docs.map((doc) => { return this.getHeroFromData(doc) }))
})
);
}
getHero(id: string): Observable<Hero | undefined> {
return this.getHeroDocument(id).valueChanges()
.pipe(
mergeMap(data => { return data ? this.getHeroFromData(data) : of(undefined) })
);
}
private getHeroFromData(data: JsonArray): Observable<Hero> {
const hero = new Hero().fromJSON(data);
if (hero.idSkill1 && hero.idSkill2 && hero.idRarity) {
return zip(
this.skillService.getSkill(hero.idSkill1),
this.skillService.getSkill(hero.idSkill2),
this.rarityService.getRarity(hero.idRarity)
).pipe(
map(res => {
console.log(res);
hero.skill1 = res[0];
hero.skill2 = res[1];
hero.rarity = res[2]
return hero;
})
)
}
return of(hero);
}
Upvotes: 0
Reputation: 2756
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().
Upvotes: 0