How to loop through a http response with another request, extending the initial response, before subscribe?

Viewed 197

I am using Angular 6 and I need to extend an array of contacts that I get from a Http request. Each contact has a userId. And I need to loop through the contacts array, making another request based on the userId so I can extend each contact with user's first name and last name. I have to mention that I have to do it before subscribe.

contacts.service.ts

findContacts(params: paramsModel): Observable<resultsModel> {
    const httpParams = this.getParams(params);

    return this.http.get<resultsModel>('/getAllContacts', {
        params: httpParams
    }).pipe(
        map((res: any) => {
            // here I get all the contacts but I need to loop through each contact and make another request based on the userId to get info about the user that belongs to each contact
            // res looks like this:
            // {
            //     [
            //         id: 1,
            //         name: test1,
            //         userId: 14
            //     ],
            //     [
            //         id: 2,
            //         name: test2,
            //         userId: 9
            //     ],
            //     ..
            // }
            // and I need to extend it to like this:
            // {
            //     [
            //         id: 1,
            //         name: test1,
            //         userId: 14,
            //         userFirstName: John,
            //         userLastName: Doe,
            //     ],
            //     [
            //         id: 2,
            //         name: test2,
            //         userId: 9,
            //         userFirstName: Jane,
            //         userLastName: Doe,
            //     ],
            //     ..
            // }
            // I tried a lot of approaches, but with no luck.. (including flatMap, switchMap, working with chaining observables, but I didn't find an example with looping before subscribe that I could reproduce so I got lost quickly).
            // this is one of them:
            // res.map(contact => {
            //     this.http.get<any>('getUserById?userId=' + contact.userId).pipe(
            //         map( user => {
            //             // using underscore for extend
            //             _.extend(contact, {
            //                 userFirstName: user.firstName,
            //                 userLastName: user.lastName
            //             });
            //             return contact;
            //         })
            //     );
            // });

            return someFunction(res, params);
        })
    );
}

contacts.data.ts

export class ContactsData extends Data {
    constructor(private contactsService: ContactsService) {
        super();
    }

    loadContacts(
        params: paramsModel
    ) {
        this.loadingSubject.next(true);
        this.contactsService.findContacts(params).pipe(
            tap(res => {
                this.entitySubject.next(res.items);
                this.paginatorTotalSubject.next(res.totalCount);
            }),
            catchError(err => of(new resultsModel([], err))),
            finalize(() => this.loadingSubject.next(false))
        ).subscribe();
    }
}

contacts-list.component.ts

export class ContactsListComponent implements OnInit {
    contactsData: ContactsData;
    ngOnInit() {
        const params = new paramsModel();
        this.contactsData.loadContacts(params);
    }
}

Solution with the help of Michał Tkaczyk:

contacts.service.ts

findContacts(params: paramsModel): Observable<resultsModel> {
    const httpParams = this.getParams(params);

    return this.http.get<resultsModel>('/getAllContacts', {
        params: httpParams
    }).pipe(
        map((res: any) => res),
        switchMap(res => {
            const requests = res.map(contact => {
                return this.http.get<any>('getUserById?userId=' + contact.userId).pipe(
                    map(userData => {
                        console.log(userData);
                        return _.extend(contact, {
                                            userFirstName: user.firstName,
                                            userLastName: user.lastName
                                });
                    })
                );
            });
            return forkJoin(requests);
        }),
        map( res => {
            return someFunction(res, params);
        })
    );
}
2 Answers

At first change your rxjs map to switchMap, then you can iterate through data from first Observable, and create an array of observables which can be returned as forkJoin in switchMap.

Code:

switchMap(res => {
  const requests = res.map(contact => {
    return this.http.get<any>('getUserById?userId=' + contact.userId);
  });

  return forkJoin(requests);
}, (resData, usersData) => [resData, usersData]),

Then you'll get all data in array (resData is data you get before switchMap, and usersData is data you get from forkJoin, you should create map then and find data for every user

map(([res, usersRes]) => {
  return res.map(contact => {
    const foundUser = usersRes.find(user => user.id === res.id);

    return {
      ...res,
      ...foundUser
    };
  });
})

You can try this code, although I don't recommend creating so much requests, better way is to get this data from API call.

EDIT:

As Oles Savluk mentioned, resultSelector in switchMap is deprecated, so you can add another pipe every observable in forkJoin:

switchMap(res => {
  const requests = res.map(contact => {
    return this.http.get<any>('getUserById?userId=' + contact.userId).pipe(
      map(userData => {
        return { ...contact, ...userData };
      })
    );
  });

  return forkJoin(requests);
})

Get all contacts -> map contacts to observable of contacts -> it emits each item -> use each item to form an http request -> on that http request pipe the map to get the result of your format making use of contact and user details -> use toArray() collect it as array -> subscribe. Returns the array of desired output.

    this.http.get<resultsModel>('/getAllContacts', { params: httpParams}).pipe(
     mergeMap(contacts=> from(contacts)),
     concatMap((contact) =>  this.http.get<any>('getUserById?userId=' + contact.userId).pipe(
                           map((userDetails) => {
                                  contact['userFirstName']=userDetails['userFirstName'];
                                  contact['userLastName']=userDetails['userLastName'];
                                 return contact
                                }))),
     toArray()).subscribe(res => console.log(res));

PS - use mergeMap if order doesn't matter instead of concatMap.

Related