Is there a way to use a observable returning function for each element of another observable array?

喜你入骨 提交于 2019-12-23 02:36:04

问题


I get an Observable<Group[]> from my Firebase collection. In this Group class is an id which I wanna use to retrieve another dataset array from Firebase, which would be messages for each unique group Observable<Message[]>.(each group has its own chat: Message[]) And it want to return an observable which hold an array of a new Type: return { ...group, messages: Message[] } as GroupWithMessages

the final goal should be Observable<GroupWithMessages[]>

getGroupWithChat(): Observable<GroupWithMessages[]> {
    const groupColl = this.getGroups(); // Observable<Group[]>

    const messages = groupColl.pipe(
      map(groups => {
        return groups.map(meet => {
          const messages = this.getMessagesFor(group.uid);
          return { messages:messages, ...group} as GroupWithMessages
        });
      })
    );

    return messages;

  }
}

and here the Message function

 getMessagesFor(id: string): Observable<Message[]> {
    return this.afs.collection<Message>(`meets/${id} /messages`).valueChanges();
}

sadly that doesnt work because when i create the new Obj I cannot bind messages:messages because messages ist vom typ Observable<Message[]>

I hope that cleares things

UPDATE: my main problem now comes down to this:

getGroupsWithMessages() {
    this.getJoinedGroups()
      .pipe(
        mergeMap(groups =>
          from(groups).pipe(
            mergeMap(group => {
              return this.getMessagesFor(group.uid).pipe(
                map(messages => {
                  return { ...group, messages } as GroupIdMess;
                })
              );
            }),
            tap(x => console.log('reaching here: ', x)),
            toArray(),
            tap(x => console.log('not reaching here = completed: ', x))
          )
        ),
        tap(x => console.log('not reaching here: ', x))
      )
      .subscribe(x => console.log('not reaching here: ', x));
  }

when i call that function my console.log is as follows:


回答1:


Not sure if I follow what you're doing here but the logic look like you'd want:

getGroupWithChat() {
    return this.getGroups.pipe(map(groups=> {
        return groups.map(group => this.getMessagesFor(group.uid));
    })).subscribe(); // trigger "hot" observable
}

Let me know if I can help further after you clarify.

UPDATE:

So it looks like you need to get the UID of the group before making the call to get the GroupMessages[]?

  1. get Group: Observable
  2. call getMessagesFor(Group.uid)

this example gets groups result$ then concatMap uses groups result$ to make the messages query

 this.getGroups().pipe(
     concatMap((group: Group) => this.getMessagesFor(group.uid))
 ).subscribe((messages: GroupWithMessages[]) => {
    console.log(messages); 
 });

You may still want to map them together but it seems like you know how to do that. concatMap waits for the first to finish, then makes the second call which you need.

Is this closer?




回答2:


Use forkJoin to wait for messages to be received for all groups. Then map the result of forkJoin to an array of GroupWithMessages like this -

getGroupWithChat(): Observable<GroupWithMessages[]> {

    return this.getGroups()
               .pipe(
                 switchMap(groups => {
                   const messagesForAllGroups$ = groups.map(group => this.getMessagesFor(group.uid));
                   return forkJoin(messagesForAllGroups$)
                          .pipe(
                            map(joined => {

                              //joined has response like -
                              //[messagesArrayForGroup0, messagesArrayForGroup1, messagesArrayForGroup2....];

                              const messagesByGroup = Array<GroupWithMessages>();

                              groups.forEach((group, index) => {
                                //assuming that GroupWithMessages has group and messages properties.
                                const gm = new GroupWithMessages();
                                gm.group = group;
                                gm.messages = joined[index];
                                messagesByGroup.push(gm);
                              });

                              return messagesByGroup;
                            })
                          )
                 })
               )    

  }



回答3:


I usually do that by splitting Observable<any[]> to Observable<any> and then mergeMap the results to inner Observable.

Something like this should work:

  getMessagesFor(id: string): Observable<number> {
    return of(1);
  }

  getGroups(): Observable<string[]> {
    return of(["1", "2"]);
  }

  getGroupWithChat() {
    this.getGroups().pipe(
      mergeMap(groups => from(groups)), // Split the stream into individual group elements instead of an array
      mergeMap(group => {
        return this.getMessagesFor(group).pipe(
          map(messages => {
            return Object.assign(group, messages);
          })
        );
      })
    );
  }

Edit:

Consider BehaviorSubject. It doesn't complete at all:

const behSub: BehaviorSubject<number[]> = new BehaviorSubject([1, 2, 3]);

setTimeout(() => {
  behSub.next([4, 5, 6]);
}, 5000);

behSub
  .pipe(
    mergeMap(arr =>
      from(arr).pipe(
        tap(), // Do something with individual items, like mergeMap to messages
        toArray() // Go back to array
      )
    )
  )
  .subscribe(console.log, null, () => {
    console.log('Complete');
  });


来源:https://stackoverflow.com/questions/56550911/is-there-a-way-to-use-a-observable-returning-function-for-each-element-of-anothe

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!