Home > database >  How to assign an object property from a request for each object in an observable array of those obje
How to assign an object property from a request for each object in an observable array of those obje

Time:10-28

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.

  • Related