Search code examples
angulartypescriptrxjstoarrayswitchmap

Rxjs switchMap + toArray


I am having a problem with rxjs.

I have a function that should do this:

  • Having a list of group ids. In the example: of(['1', '2'])
  • For each one of them fetch the list of chats
  • Return merged list of chats

When execution reaches toArray nothing happens, no result.

Code

  get chats$(): Observable<Chat[]> {
    return of(['1', '2']).pipe(
      filter(groupIds => !!groupIds && groupIds.length > 0),
      switchMap(groupIds => groupIds),
      switchMap(groupId => getGroupChats(groupId)), // fetch list of chats for the group id
      toArray(),
      map(doubleList => {
        return ([] as Chat[]).concat(...doubleList); // merge chat lists
      })
    );
  }

I have tried this too:

get chats$(): Observable<Chat[]> {
    return of(['1', '2']).pipe(
      filter(groupIds => !!groupIds && groupIds.length > 0),
      map(groupIds =>
        groupIds.map(groupId => getGroupChats(groupId))
      ),
      switchMap(chatList$ =>
        forkJoin(chatList$).pipe(
          map(doubleList => {
            return ([] as Chat[]).concat(...doubleList);
          })
        )
      )
    );
}

Test

Test response is: Error: Timeout - Async callback was not invoked within 5000ms

describe("WHEN: get chats$", () => {
  const CHAT_MOCK_1: Chat = {
    id: "1",
  };
  const CHAT_MOCK_2: Chat = {
    id: "2",
  };

  it("THEN: get chats$ should return chat list", (done) => {
    service.chats$
      .subscribe((data) => {
        expect(data.length).toEqual(2);
        expect(data[0]).toEqual(CHAT_MOCK_1);
        expect(data[1]).toEqual(CHAT_MOCK_2);
        done();
      })
      .unsubscribe();
  });
});

Solution

  • Finally this is what I have done (and it works):

    • use plain Array.map to transform our group ids array to a list of observables, each observable containing the array of chats for that group.
    • use forkJoin to get final emitted value of each observable of the generated Array.

    Code

    get chats$(): Observable<Chat[]> {
        return this.groupsIds$.pipe(
            skipUntil(this._groupsLoaded$),
            switchMap((ids) => {
                const chatsList: Observable<Chat[]>[] = ids.map((id) =>
                    this.getGroupChats$(id)
                );
    
                return forkJoin([...chatsList]).pipe(
                    map((list) => ([] as Chat[]).concat(...list))
                );
            })
        )
    }
    

    I still have some doubts about why this works and not the previous versions, if someone could explain that would be great.

    As a conclusion: do not concat multiple switchMap.