I want to display in a view the next upcoming event a user is registered to.
To do so I need first to retrieve the closest event (in time) the user is registered to and then retrieve the information of this event.
Has the list of event a user is registered to is dynamic and so is the event information I need to use two Observable in a row.
So I tried to use concatMap but I can see that the getEvent function is called 11 times... I don't understand why and how I could do this better.
Here is my controller
//Controller
nextEvent$: Observable<any>;
constructor(public eventService: EventService) {
console.log('HomePage constructor');
}
ngOnInit(): void {
// Retrieve current user
this.cuid = this.authService.getCurrentUserUid();
this.nextEvent$ = this.eventService.getNextEventForUser(this.cuid);
}
The EventService (which contains the getEvent function called 11 times)
// EventService
getEvent(id: string, company?: string): FirebaseObjectObservable<any> {
let comp: string;
company ? comp = company : comp = this.authService.getCurrentUserCompany();
console.log('EventService#getEvent - Getting event ', id, ' of company ', comp);
let path = `${comp}/events/${id}`;
return this.af.object(path);
}
getNextEventForUser(uid: string): Observable<any> {
let company = this.authService.getCurrentUserCompany();
let path = `${company}/users/${uid}/events/joined`;
let query = {
orderByChild: 'timestampStarts',
limitToFirst: 1
};
return this.af.list(path, { query: query }).concatMap(event => this.getEvent(event[0].id));
}
And finally my view
<ion-card class="card-background-image">
<div class="card-background-container">
<ion-img src="sports-img/img-{{ (nextEvent$ | async)?.sport }}.jpg" width="100%" height="170px"></ion-img>
<div class="card-title">{{ (nextEvent$ | async)?.title }}</div>
<div class="card-subtitle">{{ (nextEvent$ | async)?.timestampStarts | date:'fullDate' }} - {{ (nextEvent$ | async)?.timestampStarts | date:'HH:mm' }}</div>
</div>
<ion-item>
<img class="sport-icon" src="sports-icons/icon-{{(nextEvent$ | async)?.sport}}.png" item-left>
<h2>{{(nextEvent$ | async)?.title}}</h2>
<p>{{(nextEvent$ | async)?.sport | hashtag}}</p>
</ion-item>
<ion-item>
<ion-icon name="navigate" isActive="false" item-left small></ion-icon>
<h3>{{(nextEvent$ | async)?.location.main_text}}</h3>
<h3>{{(nextEvent$ | async)?.location.secondary_text}}</h3>
</ion-item>
<ion-item>
<ion-icon name="time" isActive="false" item-left small></ion-icon>
<h3>{{(nextEvent$ | async)?.timestampStarts | date:'HH:mm'}} - {{(nextEvent$ | async)?.timestampEnds | date:'HH:mm'}}</h3>
</ion-item>
</ion-card>
The
this.af.list(path, { query: query }).concatMap(event => this.getEvent(event[0].id))
is a coldObservable
. This means that each time you perform a subscription on it, it will re-execute the underlying stream, which means re-calling thegetEvent
method.async
implicitly subscribes to theObservable
, which is why if you count up(nextEvent$ | async)
calls in your template, you will see where the 11 comes from.&tldr; You need to share the subscription to the stream:
The above will connect the stream the first time it is subscribed to but will then subsequently share that subscription between all of the subscribers.