Reputation: 11
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
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