Fred Manders
Fred Manders

Reputation: 11

double combineLatest doesn't emit update

In my project there are activities that people have created, joined, bookmarked or organized. I've read a lot of these question already. But most of the code was less complex or people forgot to subscribe...

I would like to get all the activities in a certain time period and then add creator information (name, image, etc) and add booleans if the user retrieving these activities has joined/bookmarked/organized this activity. The code I used before would provide live updates (ex. I join an activity, by adding my userId to the participants array and the activity.joined would update to true).

Previous code:

public getActivities(user: UserProfileModel): Observable<Array<ActivityModel>> {
    const now: number = moment().startOf('day').unix();
    const later: number = moment().startOf('day').add(30, 'day').unix();
    return this.afs.collection<ActivityModel>(`cities/${user.city.id}/activities`, ref => ref
        .where('datetime', '>=', now)
        .where('datetime', '<=', later))
        .valueChanges({ idField: 'id' })
        .pipe(
            map(activities => activities.map(activity => {
                const bookmarked = activity.bookmarkers ? activity.bookmarkers.includes(user.uid) : false;
                const joined = activity.participants ? activity.participants.includes(user.uid) : false;
                const organized = activity.organizers ? activity.organizers.includes(user.uid) : false;
                return { bookmarked, joined, organized, ...activity } as ActivityModel;
        }))
    );
}

The I wanted to add the creator as an observable object, so their latest changes in name or profile picture would be shown. But with this code change, my getActivities doesn't emit any updates anymore...

My new code:

public getActivities(user: UserProfileModel): Observable<Array<CombinedActivityCreatorModel>> {
    const now: number = moment().startOf('day').unix();
    const later: number = moment().startOf('day').add(30, 'day').unix();
    return this.afs.collection<ActivityModel>(`cities/${user.city.id}/activities`, ref => ref
        .where('datetime', '>=', now)
        .where('datetime', '<=', later))
        .valueChanges({ idField: 'id' })
        .pipe(
            concatMap(activities => {
                const completeActivityData = activities.map(activity => {
                    const activityCreator: Observable<UserProfileModel> = this.getCreator(activity.creator);
                    const bookmarked = activity.bookmarkers ? activity.bookmarkers.includes(user.uid) : false;
                    const joined = activity.participants ? activity.participants.includes(user.uid) : false;
                    const organized = activity.organizers ? activity.organizers.includes(user.uid) : false;
                    return combineLatest([
                        of({ bookmarked, joined, organized, ...activity }),
                        activityCreator
                    ]).pipe(
                        map(([activityData, creatorObject]: [ActivityModel, UserProfileModel]) => {
                            return {
                                 ...activityData,
                                 creatorObject: creatorObject
                            } as CombinedActivityCreatorModel;
                        })
                    );
                });
            return combineLatest(completeActivityData);
        })
    );
}

The code has become a bit complex, that I don't see the solution myself. Anybody that can offer some assistance?

Upvotes: 1

Views: 268

Answers (1)

satanTime
satanTime

Reputation: 13539

Looks like one of activityCreator doesn't emit a value, combineLatest requires all observables to emit at least once.

I would recommend you to debug how activityCreator behaves. If it's fine that it doesn't emit you have 2 options: startWith to set a value for an initial emit, or defaultIfEmpty, it emits in case if stream is going to be closed without any emit.

activityCreator = this.getCreator(activity.creator).pipe(
  // startWith(null), // for example if you want to trigger combineLatest.
  // defaultIfEmpty(null), // in case of an empty stream.
);

another thing is concatMap it requires an observable to complete, only then it switches to the next one, parhaps mergeMap or switchMap fits here better.

Try the code below and add its output to the comments. Thanks.

                        const activityCreator: Observable<UserProfileModel> = this.getCreator(activity.creator).pipe(
                            tap(
                                () => console.log('getCreator:emit'),
                                () => console.log('getCreator:error'),
                                () => console.log('getCreator:completed'),
                            ),
                        );

Upvotes: 1

Related Questions