Chaining map within a RxJS Pipe does not work

Viewed 56

I'm trying to transform a GET request from the YouTube Search API with the following function.

The first map should get me the items within the response Object. With the second map I want to map the items to my Custom Class SearchResult. But for some reason there is always the complete list of items within the the second map, instead of iterating over the single Elements. And as a result the pipe won't return an Observable with an Array of SearchResult Elements.

Angular 13 RxJS 7.5

  search(query: string) : Observable<SearchResult[]>{
    const params: string = [
      `q=${query}`,
      `key=${this.apiKey}`,
      `part=snippet`,
      `type=video`,
      `maxResults=10`
    ].join('&');
    const queryUrl = `${this.apiUrl}?${params}`;
    return this.http.get(queryUrl).pipe(
      map((response : any) => response.items),
      map(item => {
        return new SearchResult({
          id: item.id.videoId,
          title: item.snippet.title,
          description: item.snippet.description,
          thumbnailUrl: item.snippet.thumbnails.high.url
        });
      }),
    )}
  }
3 Answers

Since your property is an array, you need to map each array element to a new SearchResult:

return this.http.get(queryUrl).pipe(
  map((response : any) => response.items),
  map(items => items.map(item => {
    return new SearchResult({
      id: item.id.videoId,
      title: item.snippet.title,
      description: item.snippet.description,
      thumbnailUrl: item.snippet.thumbnails.high.url
    });
  })),
)}

The first map returns the items array from the response, and the second one should use the Array.map() function to map the array items to the required model.

The RxJS's map operator doesn't iterate through the array items, instead, it maps each value emitted in the stream to something else.

You can handle like the following:

search(query: string): Observable<SearchResult[]> {
  const params: string = [
    `q=${query}`,
    `key=${this.apiKey}`,
    `part=snippet`,
    `type=video`,
    `maxResults=10`,
  ].join('&');
  const queryUrl = `${this.apiUrl}?${params}`;
  return this.http.get(queryUrl).pipe(
    map((response: any) => response.items),
    map((items) =>
      items.map(
        (item) =>
          new SearchResult({
            id: item.id.videoId,
            title: item.snippet.title,
            description: item.snippet.description,
            thumbnailUrl: item.snippet.thumbnails.high.url,
          })
      )
    )
  );
}

You can read more about it here: https://rxjs.dev/api/operators/map

You're basically trying to flatten (or unwrap) response.items array into individual emissions. Right now, you're just returning an array from the first map().

There're more ways you can unwrap the array. The more obvious one is this:

return this.http.get(queryUrl).pipe(
    mergeMap((response : any) => from(response.items)),
    map(item => {
      return new SearchResult({
        id: item.id.videoId,
        title: item.snippet.title,
        description: item.snippet.description,
        thumbnailUrl: item.snippet.thumbnails.high.url
      });
    }),
  )};

from() will iterate the array and emit each item as a separate emission. Since from() returns an Observable you'll need to use mergeMap() instad of just map().

Another way is just using mergeMap():

mergeMap((response : any) => response.items),

An array is for RxJS operators something called ObservableInput and it will iterate the array automatically and reemit its items.

Related