I've tried looking all over at different resources for trying to figure this out (looked at mergeMap, switchMap, etc) but can't get myself to resolving the issue I'm having and am still relatively new to RxJs. I'd involve my attempts in the details of this post, but I think it would over clutter and make my question more confusing.
I'm making a travel tracking app. After a user logs in, it takes them to a page that displays all of their trips (via http.get() to my backend API). Each trip object has an array of destinations. After the frontend receives the trips as an observable I'd like to then make a request to Google Place Details API and assign a photo url for each trip by using the destinationId from the first destination in that trip's destination array.
NOTE: all functions work properly (aside from assigning the trip ImgURL correctly)
A Trip looks like this:
Trip = {
id: 1,
Title: 'Going to New York',
Members[ ... ],
Destinations: [
{
id: 'abc123', // this is a specific ID used for Google Place Details API request to get relevant photo
city: 'New York City',
region: 'New York',
country: 'United States'
}
],
imgURL: '' // gets assigned from Google request after trips are retrieved from backend
// other irrelevant properties
}
This is the method for getting trips:
getTrips(): Observable<Trip[]> {
return this.http.get<Trip[]>(this.tripsUrl, this.headers).pipe(
map((trips: Trip[]) =>
this.filterTripsByUsername(trips, this.auth.currentUser.username) // this is bad practice.. I know it should be filtered from backend :(
),
// DO SOMETHING HERE? mergemap? switchmap?
// for each trip I'd like to assign the ImgURL to the result from getTripImgURL()
retry(2),
catchError(this.handleError<Trip[]>('getTrips()', []))
);
}
This is the method for returning the imgURL as an observable:
// functions properly as is, but willing to change if necessary or 'better' to
getTripImgURL(placeId: string): Observable<string> {
return new Observable((obs) => {
let getGooglePhotoURL = function (placeResult: any, status: any) {
if (status != google.maps.places.PlacesServiceStatus.OK) {
obs.error(status);
} else {
var imgUrl = 'assets/images/trips/default.jpg';
// if google has any photos for the given placeId
if (placeResult.photos.length !== 0) {
imgUrl = placeResult.photos[0].getUrl({
maxWidth: 500, // at least set one or the other - mandatory
maxHeight: undefined,
});
}
obs.next(imgUrl);
obs.complete();
}
};
var PlacesService = new google.maps.places.PlacesService(
document.getElementById('empty')! as HTMLDivElement
);
PlacesService.getDetails(
{
placeId: placeId,
},
getGooglePhotoURL
);
});
}
Keep in mind I'll need to replicate similar functionality for a single trip.. getTrip()
getTrip(id: number): Observable<Trip> {
const url = `${this.tripsUrl}/${id}`;
return this.http
.get<Trip>(url, this.headers)
.pipe(
// I'd like to assign the Trip's ImgURL to the result from getTripImgURL()
retry(2),
catchError(this.handleError<Trip>('getTrip()')));
}
THANK YOU IN ADVANCE. Any input on changing implementation or what not is also appreciated.
I've tried using mergeMap (new concept for me) to use the given Trip[] and return a trip observable for each trip that had a subscription to getTripImgURL() which would assign the appropriate google photo url for each trip.ImgURL. But nothing ever worked properly with Observable/Subscription async behavior. I just don't know how to go about RxJS properly as its still very new to me.
CodePudding user response:
My approach would be to use a switchMap
which triggers a forkJoin
containing an array of getTripImgURL
Observables. The forkJoin
will wait for the array of inner Observables to complete.
getTrips(): Observable<Trip[]> {
return this.http.get<Trip[]>(this.tripsUrl, this.headers).pipe(
map((trips: Trip[]) => {
// do your filtering here, but return the processed trips!
return trips
}),
switchMap(trips => forkJoin(trips.map(trip =>
this.getTripImgURL(trip.id).pipe(
map(imgUrl => ({ ...trip, imgUrl }))
// ^^^^^^ add imgUrl to the trip
)
))),
retry(2),
catchError(this.handleError<Trip[]>('getTrips()', []))
);
}
For each trip
in trips
, it would call getTripImgURL
and add imgUrl
to trip
inside the map
.
Just be careful that you actually return trips
in the first map
. Otherwise, it will pass undefined
down the pipe.