Reputation: 2445
I have an Angular service with the following method:
public getItem(itemId: string): Observable<IItem> {
if (itemId== null) {
return of(null);
}
return this.store.pipe(
select(getItemFromStore(itemId)),
tap(item=> {
if (item=== undefined) {
this.store.dispath(new GetItemAction({itemId: itemId}}));
}
}),
);
}
And an effect to catch the action:
@Effect()
public getItem$: Observable<Action> = this.actions
.pipe(
ofType(ActionTypes.GET_ITEM),
map((action: GetItemAction) => action.payload),
exhaustMap(payload => {
return this.httpClient.get(`api/items/` + payload.itemId).pipe(
map(item => {
return new GetItemSuccessAction({ itemId: payload.itemId, item });
}),
catchError(error => {
return of(new GetItemErrorAction({ error: error }));
})
);
})
);
This all works fine and dandy except if I were to request two different items at the same time, the exaustMap would ignore the second item and it would never get retrieved.
How can I write this so requests for the same item are only loaded once but requests for two items at the same time will both load?
An example:
This.store.dispatch(new GetItemAction({itemId: '1'});
This.store.dispatch(new GetItemAction({itemId: '1'});
This.store.dispatch(new GetItemAction({itemId: '2'});
If all 3 actions were dispatched, there should be 2 http requests, one for item 1 and one for item 2.
Upvotes: 1
Views: 264
Reputation: 15505
You can use the distinct
operator for this.
Returns an Observable that emits all items emitted by the source Observable that are distinct by comparison from previous items.
For example:
const s = new Subject();
const source = s.pipe(
distinct(p => p.id),
concatMap(p => of(p)) // instead of `of(p)`, call the service
);
source.subscribe(x => console.log(x));
s.next({id: 1, text: 'AAA'})
s.next({id: 1, text: 'BBB'})
s.next({id: 2, text: 'CCC'})
s.next({id: 2, text: 'DDD'})
s.next({id: 1, text: 'EEE'})
s.next({id: 4, text: 'FFF'})
Logs:
{id: 1, text: "AAA"}
{id: 2, text: "CCC"}
{id: 4, text: "FFF"}
This would also mean you wouldn't be able to fetch id 1
later, if this is something you want to do, you can use groupBy
:
const s = new Subject();
const source = s.pipe(
groupBy(p => p.id),
mergeMap(p => p.pipe(
exhaustMap(p => of(p).pipe(delay(1000))),
)
),
);
source.subscribe(x => console.log(x));
s.next({id: 1, text: 'AAA'})
s.next({id: 1, text: 'BBB'})
s.next({id: 2, text: 'CCC'})
s.next({id: 2, text: 'DDD'})
s.next({id: 1, text: 'EEE'})
s.next({id: 4, text: 'FFF'})
setTimeout(() => {
s.next({id: 1, text: 'AAA'})
}, 2000)
Logs:
{id: 1, text: "AAA"}
{id: 2, text: "CCC"}
{id: 4, text: "FFF"}
After 2 seconds:
{id: 1, text: "AAA"}
Upvotes: 1