I have this Angular app in which I want to create a custom store for chat objects.
(codesandbox: https://codesandbox.io/s/chat-store-yw4yz)
Note: I know there are tools like ngrx but this is part of a bigger project and I would prefer to use this custom implementation so I understand better the behaviour of the store.
The idea to make it quick and efficient is to have into the store two types of records:
key = chat id
and value = chat object
key = groupId
and value = chatId
So I have this interface definition:
export interface ChatStoreModel {
chats: Map<number, Chat>;
chatsByGroup: Map<number, number[]>;
}
When a chat object is saved into the Store two things should happen:
chats
chatsByGroup
in the corresponding groupIdIt is not working to get the chat objects from a group. I do the following:
chatsByGroup
using groupIdchats
Code:
getGroupChats$(groupId: number): Observable<Chat[]> {
return this._store.select("chatsByGroup").pipe(
map((chatsByGroup) => chatsByGroup.get(groupId) || []),
switchMap((ids) => {
if (!ids || !ids.length) {
return of([]);
}
const chatsList$: Observable<Chat>[] = ids.map((id) =>
this._store.select("chats").pipe(map((chats) => chats.get(id)))
);
return forkJoin([...chatsList$]).pipe(
map((list) => {
return ([] as Chat[]).concat(...list);
})
);
}),
shareReplay(1)
);
}
Debugging this code, it reaches the line with return forkJoin
and ids have the list of chats but it never reaches the line map((list) => {
so the app does not display the chat list.
Any help will be appreciated, thanks!
whenever any of that observables completes without emitting any value, forkJoin will complete at that moment as well and it will not emit anything either
This is from the RxJS ForkJoin Doc. My guess is that the one of the Observable that you pass to the forkJoin
completes without emitting anything, thus forkJoin
instantly completes without emitting anything.
Try using defaultIfEmpty
operator to be sure that this situation doesn't happen. I suggest doing something like so:
const chatsList$: Observable<Chat>[] = ids.map((id) =>
this._store.select("chats").pipe(map((chats) => chats.get(id))),
defaultIfEmpty(null),
);
forkJoin will wait for all passed observables to complete
This is again from the docs. This means that all of the Observables that you pass to the forkJoin
have to complete in order for the forkJoin
to emit value itself.
My guess is that this._store.select("chats").pipe(map((chats) => chats.get(id))),
this line will run endlessly so you need to provide take(1)
for it to be completed, otherwise forkJoin
won't emit anything. So try something like this:
const chatsList$: Observable<Chat>[] = ids.map((id) =>
this._store.select("chats").pipe(map((chats) => chats.get(id))),
take(1),
defaultIfEmpty(null),
);
Lastly if you don't want to use take(1)
, by which I mean you want to be subscribed to the Observable as long as it is emitting values, you might want to use combineLatest
instead of forkJoin
. That's because forkJoin
waits for the completion to emit something, while combineLatest
will emit the latest values of the provided Observables.