I have a complex observable chain in Angular using RxJS, and I'm facing unexpected behavior with nested switchMap and docData calls. The observable chain involves enriching the activities fetched from this.stream_service.readFeed() with additional user data using docData. However, I'm encountering unexpected behavior with the docData call, specifically with the tap and switchMap operators.
I anticipate the docData next and combineLatest next logs to occur only once, as there is only one item in the activity_response array.
Despite the observable emitting values, the tap and switchMap operators are logging more times than expected. The docData next and combineLatest next logs appear twice instead of once.
Here's this code:
private post_activity$ = toObservable(this.post).pipe(
// Use switchMap to switch to a new observable based on the latest value of post$
switchMap((post) => {
// Check if post is falsy (null, undefined, etc.)
if (!post) {
// If post is falsy, return an EMPTY observable (essentially a complete and empty observable)
return EMPTY;
}
// Call the current_user() function to get the current user
const current_user = this.current_user();
// Determine the feed_type based on whether the current user matches the post's user_id
const feed_type =
current_user && current_user?.uid === post.user_id ? 'user' : 'public';
// Return the result of calling this.stream_service.readFeed()
return this.stream_service.readFeed(
feed_type,
post.user_id,
1,
post.stream_activity_id
);
}),
tap((post_activity) => {
console.log('%cPost Activity', 'color: pink', post_activity);
}),
switchMap((activity_response) => {
// Check if activity_response is falsy
if (!activity_response) {
// If it is, return an EMPTY observable
return EMPTY;
}
// Call a service method to enrich the activities in activity_response
if (activity_response.length > 0) {
const activity = activity_response[0]; // 1 activity
console.log('Activity', activity); // This will log once
return docData(doc(this.firestore, 'users', activity.actor), {
idField: 'id',
}).pipe(
tap({
next: (user) => console.log('docData next', user),
error: (error) => console.log('docData error', error),
complete: () => console.log('docData complete'),
}), // this will log twice
switchMap((user: any) => {
const activity_user = {
username: user.username,
pronouns: user.pronouns,
avatar_url: user.avatar_url,
photo_url: user.photo_url,
};
return of({
user: activity_user,
activity: activity,
liked:
!!activity.own_reactions?.like &&
activity.own_reactions.like[0].user_id ===
this.user_service.current_user()?.uid,
});
}),
tap({
next: (activities) => console.log('combineLatest next', activities),
error: (error) => console.log('combineLatest error', error),
complete: () => console.log('combineLatest complete'),
}) // this will log twice too
);
} else {
return EMPTY;
}
}),
tap(() => {
this.count.set(this.count() + 1);
console.log('%cCount', 'color: lime', this.count()); // Count 2
})
);
What could be causing the unexpected behavior in the observable chain and how can I ensure that the docData next and combineLatest next logs occur only once as expected?
Any insights or guidance on debugging and resolving this issue would be greatly appreciated. Thanks!
Additional Info:
I added some code snippets to check if isolating it would log the docData() twice. However, I ended up making things even more puzzling, as I found out that not only did the docData() log only once (as expected), but my code in the switchMap also logged only once. It's pretty mind-boggling!
data$ = docData(
doc(this.firestore, 'users', activity.actor)
).pipe(
tap({
next: (user) => console.log('docData next', user),
error: (error) => console.log('docData error', error),
complete: () => console.log('docData complete'),
}) // this will log twice
);
private readonly data = toSignal(this.data$);
From what you've shown, the only cause could be that docdata is emitting multiple values. It is the only way the tap could log twice. If the user is the same each time, then perhaps docdata is returning it twice for some reason.
Try changing the pipe to:
Evaluate the results as follows:
If the first tap prints 2x and the 2nd tap prints 1x, then this means docData is emitting the same username twice. Something for you to debug.
If the both taps print 2x, then docData is emitting 2 different users (different username, but same id from your query). Again something for you to debug.
Notice both cases indicate the issue is within docData, but they should help narrow down the cause.
Edit: I'm not familiar with
toSignalbut there's a good chance it is deduping (effectively usingdistinctUntilChanged), which is why that version of your code prints the tap twice but things subscribed to the signal only get called once.