Assigning object properties from a request to every item in an observable array of objects using RxJS

Despite my efforts to search various resources like mergeMap and switchMap, I am unable to solve the problem I'm facing as I am still new to RxJs. While I would like to provide more details about my attempts in this post, I fear it may complicate my question further.

I am currently developing a travel tracking app where users can view their trips after logging in. The frontend fetches all trip data from the backend API via http.get(). Each trip contains an array of destinations. Once the frontend receives the trip data as an observable, I intend to make additional requests to the Google Place Details API to assign a photo URL for each trip based on the destinationId of the first destination in the array.

It is important to note that all functions are working correctly except for assigning the trip ImgURL.

An example Trip object looks like this:

Trip = {
  id: 1,
  Title: 'Going to New York',
  Members[ ... ],
  Destinations: [
    { 
      id: 'abc123', // used to obtain relevant photo from Google Place Details API
      city: 'New York City',
      region: 'New York',
      country: 'United States'
    }
  ],
  imgURL: '' // to be assigned from Google request after fetching trips from backend
  // other irrelevant properties
}

This is the method for retrieving trips:

getTrips(): Observable<Trip[]> {
  return this.http.get<Trip[]>(this.tripsUrl, this.headers).pipe(
        map((trips: Trip[]) =>
          this.filterTripsByUsername(trips, this.auth.currentUser.username) 
        ),
        // NEED HELP HERE - mergemap? switchmap?
        // goal is to assign the ImgURL for each trip using getTripImgURL()
        retry(2),
        catchError(this.handleError<Trip[]>('getTrips()', []))
  );
}

This is the method for obtaining the imgURL as an observable:

// works fine, but open to improvements
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 (placeResult.photos.length !== 0) {
            imgUrl = placeResult.photos[0].getUrl({
              maxWidth: 500, 
              maxHeight: undefined,
            });
          }
          obs.next(imgUrl);
          obs.complete();
        }
      };

      var PlacesService = new google.maps.places.PlacesService(
        document.getElementById('empty')! as HTMLDivElement
      );
      PlacesService.getDetails(
        {
          placeId: placeId,
        },
        getGooglePhotoURL
      );
    });
  }

Please keep in mind that similar functionality will be needed for individual trips when implementing getTrip().

  getTrip(id: number): Observable<Trip> {
    const url = `${this.tripsUrl}/${id}`;

    return this.http
      .get<Trip>(url, this.headers)
      .pipe(
         // aim is to assign the Trip's ImgURL using getTripImgURL()
         retry(2), 
         catchError(this.handleError<Trip>('getTrip()')));
  }

Thank you for your assistance. Any suggestions on improving implementation are also welcome.

My attempts with mergeMap to create subscriptions for each trip linking to getTripImgURL() did not yield the expected results due to challenges with asynchronous behavior. Mastering RxJS continues to be a learning curve given its novelty to me.

Answer №1

If I were to tackle this issue, my strategy would involve utilizing a switchMap function that triggers a forkJoin operation encompassing an array of getTripImgURL Observables. The forkJoin will wait for all inner Observables in the array to complete.

fetchTrips(): Observable<Trip[]> {
    return this.http.get<Trip[]>(this.tripsUrl, this.headers).pipe(
        map((trips: Trip[]) => {
          // Implement your filtering logic here and ensure you return the processed trips
          return trips
        }),
        switchMap(trips => forkJoin(trips.map(trip =>
            this.getTripImgURL(trip.id).pipe(
                map(imgUrl => ({ ...trip, imgUrl }))
                //                        ^^^^^^ include imgUrl with the trip object
            )
        ))),
        retry(2),
        catchError(this.handleIssue<Trip[]>('fetchTrips()', []))
  );
}

For every trip within the collection of trips, it will invoke getTripImgURL and append the resulting imgUrl to the respective trip within the map.

It is crucial to ensure that you properly return trips within the initial map. Failing to do so may propagate undefined further down the pipeline.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Learn the art of bypassing TypeScript errors using @ts-ignore!

I recently encountered an issue when trying to import a pure JavaScript library into a TypeScript project, resulting in the error message: Could not find a declaration file for module xxx. After some research, I learned that this error can be suppressed u ...

Troublesome bug in Angular 7: [ngModel] functionality fails to cooperate with mat-select

Having trouble implementing ngModel in the following scenario: Check out the template code below: <mat-select [ngModel]="data.dataObject[0].phase"> <mat-option *ngFor="let phase of possiblePhases" [value]=" ...

The error message displayed in the Typescript Playground is stating that there is no property named 'values' on the type 'ObjectConstructor'

Could someone please take a look at this link? I'm encountering an error with the playground and I'm not sure why. Click here ...

Managing Import Structure in Turborepo/Typescript Package

I am currently working on creating a range of TypeScript packages as part of a Turborepo project. Here is an example of how the import structure for these packages looks like: import { Test } from "package-name" import { Test } from "package ...

Using LitElement: What is the best way to call functions when the Template is defined as a const?

When the template is defined in a separate file, it's not possible to call a function in the component. However, if the template is defined directly as returning rendered HTML with this.func, it works. How can one call a function when the template is ...

Creating a Utils class in Vue.js with seamless access to Vuex through this.$store

I have a situation where I need to retrieve state from the Vuex store using this.$store. After some research, I discovered that creating a custom plugin with an installed instance method might be the solution. Here is my plugin implementation: index.ts i ...

Angular 2 - Ensuring service executes only when boolean condition is met

I am currently dealing with a navigation menu that utilizes the ng2-page-scroll module. When scrolling through the page using hashtag links, I encountered an issue. If I navigate across routes, there is a delay in loading the data. As a result, the servic ...

Prisma: Utilizing the include option will retrieve exclusively the subobject fields

I created a function to filter the table building and optionally pass a Prisma.BuildingInclude object to return subobjects. async describeEntity(filter: Filter, include?: Prisma.BuildingInclude): Promise<CCResponse> { try { const entity = await ...

Angular 2 Issue: Error Message "Cannot bind to 'ngModel'" arises after FormsModule is added to app.module

I've been struggling with the data binding aspect of this tutorial for over a day now. Here's the link to the tutorial: https://angular.io/docs/ts/latest/tutorial/toh-pt1.html The error I keep encountering is: Unhandled Promise rejection: Tem ...

Tips for utilizing innerHTML in TypeScript code within an Angular 2 application

Is there a way to utilize innerHTML from TypeScript code in Angular 2 RC4? I'm facing an issue: I need to dynamically add precompiled HTML code when a specific button is clicked. For instance: TypeScript code private addHTML() { // not sure how ...

Troubleshooting issue with Vue Class Component and Vuex-class causing ESLint error

I am interested in utilizing vuex-class to bind helpers for vuex and vue-class-component However, an error message is displayed: Error: Parsing error - Using the export keyword between a decorator and a class is not allowed. Please use `export @dec class ...

Struggling to bring in components in ReactJS

My journey with ReactJS has just begun, and I've encountered some issues with the code that I believe should work but doesn't. To start off, I set up a new ReactJS project using the npm command create-react-app. Following this, I installed Googl ...

obtain data from an array using Angular 5

i need assistance with retrieving values from an array. Below is my code snippet: this.RoleServiceService.getRoleById(this.id).subscribe(data => { this.roleData.push(data['data']); console.log(this.roleData); }) however, the resulting ar ...

Exploring the potential of TypeScript with native dynamic ES2020 modules, all without the need for Node.js, while also enhancing

I have a TypeScript application that utilizes es16 modules, with most being statically imported. I am now looking to incorporate a (validator) module that is only imported in debug mode. Everything seems to be functioning properly, but I am struggling to f ...

Retrieve the overall number of job openings from the Github Job API

I have successfully created an Angular application that mirrors the functionality of However, I encountered a limitation where only 50 positions are available per page, To fetch additional jobs beyond the initial 50, I need to append "?page=X" to another ...

Can you explain the concept of server-side rendering with Angular 2?

I've heard that angular2 is utilized for server-side rendering, and I'm curious to learn more about it. Here are some questions I have regarding this concept: 1. What exactly is server-side rendering? 2. What issues does it address? 3. What a ...

Transforming Angular application into a microfrontend architecture

I've successfully developed an Angular application and now I'm looking to transform it into a microfrontend application using the single SPA approach. Can someone please advise me on the necessary steps to achieve this? Also, I'd appreciate ...

Transforming the string attribute of an object within a JavaScript array through mapping

Here is an array of objects: { "attr1": 123, "attr2": "a.end", }, { "attr1": 123, "attr2": "b.start", } The task is to remove the first part of the attr2 string up to and including the '.& ...

What is the best way to exhibit information from a get request within an option tag?

My GET request is fetching data from a REST API, and this is the response I receive: { "listCustomFields": [ { "configurationType": null, "errorDetails": null, "fieldId" ...

Allowing the OPTIONS method in CORS when sending a REST request from AJAX to a WCF Service

After spending 7 hours scratching my head, I am still unable to figure this out. Despite my extensive search on the web, no luck has come my way. My Angular App is sending requests to a WCF command-line hosted service application. To bypass CORS, I utilize ...