0

Problem: When I fetch data from the server I get some object in the format:

{
  list: {
    pagination: {},
    entries: []
  }
}

I need to go through entries and change them. To change them I need to call some REST API for each entry (for example: in the first call I will get ids, and for each id, I need to get real data). After all REST calls are finished for each object I need to change its data. so entries[i] = from id to real data.

I resolved the problem with Promises but I want to do that with Observables.

I tried to use map and concatMap from rxjs but without success.

Code:

public getUsers(queryBody?: SearchRequest): Observable<PersonPaging> {
    const searchRequest =
      queryBody ||
      this.searchService.makeSearchRequest(
        new Query({ type: ContentModel.TYPE_PERSON })
      );

    return this.searchService
      .search(searchRequest)
      .pipe(
        switchMap(async (resultSetPaging: ResultSetPaging) => {
          const users: PersonPaging = {
            list: {
              pagination: resultSetPaging.list.pagination,
              entries: []
            }
          };

          for (const rowEntry of resultSetPaging.list.entries) {
            const { properties } = rowEntry.entry;

            const personEntry = await this.getUser(
              properties[ContentModel.PROP_USERNAME]
            ).toPromise();

            const person: Person = personEntry.entry;
            person.avatarId = this.getUserAvatarImage(person);

            users.list.entries.push(personEntry);
          }

          return users;
        })
      );
  }

END OF QUESTION

CODE IN PROGRESS:

  return this.searchService.search(searchRequest).pipe(
      map((resultSetPaging: ResultSetPaging) => {
        return from(resultSetPaging.list.entries).pipe(
          concatMap(rowEntry => {
            const { properties } = rowEntry.entry;
            return from(
              this.getUser(properties[ContentModel.PROP_USERNAME])
            ).pipe(
              map(personEntry => {
                const person: Person = personEntry.entry;
                person.avatarId = this.getUserAvatarImage(person);
                return { person };
              })
            );
          })
        );
      })
    );

With this code, I get all users with modified data but I don't get the object that I want with pagination. And it's call x times (x - number of users/entries)

sbakic
  • 33
  • 5
  • This [ANSWER](https://stackoverflow.com/a/64290137/1858357) should help you. It doesn't deal specifically with pagination, but shows how you can return a combined set of data from multilple http calls. [StackBlitz Sample](https://stackblitz.com/edit/so-64284681?file=src/app/app.component.ts) – BizzyBob Oct 13 '20 at 22:28

1 Answers1

1

I'm not 100% sure how you expect this to be formatted, but this should get you close. It sounds like all you really need to do is reduce your list of Person into an array on users.list.entries? That would look something like this:

return this.searchService.search(searchRequest).pipe(
  map((resultSetPaging: ResultSetPaging) => {
    return from(resultSetPaging.list.entries).pipe(
      concatMap(rowEntry => {
        const { properties } = rowEntry.entry;
        return from(
          this.getUser(properties[ContentModel.PROP_USERNAME])
        ).pipe(
          map(personEntry => {
            const person: Person = personEntry.entry;
            person.avatarId = this.getUserAvatarImage(person);
            return { person };
          })
        );
      }),
      reduce(
        (users, personEntry) => {
          users.list.entries.push(personEntry);
          return users;
        },
        {
          list: {
            pagination: resultSetPaging.list.pagination,
            entries: []
          }
        }
      )
    );
  })
);

or maybe slightly cleaner (it's the same thing though)

return this.searchService.search(searchRequest).pipe(
  map((resultSetPaging: ResultSetPaging) =>
    from(resultSetPaging.list.entries).pipe(
      concatMap(rowEntry => {
        const { properties } = rowEntry.entry;
        return from(
          this.getUser(properties[ContentModel.PROP_USERNAME])
        );
      }),
      map(personEntry => {
        const person: Person = personEntry.entry;
        person.avatarId = this.getUserAvatarImage(person);
        return { person };
      }),
      reduce(
        (users, personEntry) => {
          users.list.entries.push(personEntry);
          return users;
        },
        {
          list: {
            pagination: resultSetPaging.list.pagination,
            entries: []
          }
        }
      )
    )
  )
);

Aside: I also wonder if your very first map, works as expected. It looks like you're returning a stream, so you might want mergeMap instead?

map((resultSetPaging: ResultSetPaging) =>
  from(resultSetPaging.list.entries).pipe(...

to

mergeMap((resultSetPaging: ResultSetPaging) =>
  from(resultSetPaging.list.entries).pipe(...
Mrk Sef
  • 7,557
  • 1
  • 9
  • 21
  • 1
    Great stuff. Thank you so much. There are so many rxjs things that I am learning. I needed mergeMap at the end as well. What I need to change is `(users, personEntry) => users.list.entries.push(personEntry),` to `(users, personEntry) => { users.list.entries.push(personEntry); return users; }`, because push returns number, and accumulator need to be as seed. – sbakic Oct 15 '20 at 19:42
  • Good catch with reduce's accumulator function. I'll update my answer. – Mrk Sef Oct 15 '20 at 22:20