Caching requests executed by Actions is a common practice. NGXS does not provide this ability out of the box, but it is easy to implement.
There are many different ways to approach this. Below is a simple example of using the store's current values and returning them instead of calling the HTTP service.
import { State, Action, StateContext } from '@ngxs/store';
import { tap } from 'rxjs/operators';
export class GetNovels {
static readonly type = '[Novels] Get novels';
}
@State<Novel[]>({
name: 'novels',
defaults: []
})
export class NovelsState {
constructor(private novelsService: NovelsService) {}
@Action(GetNovels)
getNovels(ctx: StateContext<Novel[]>) {
return this.novelsService.getNovels().pipe(tap(novels => ctx.setState(novels)));
}
}
Imagine that this state of novels contains only minimal information about them such as ID and name. When the user selects a particular novel - he is redirected to a page with full information about this novel. We want to load this information only once. Let's create a state and call it novelsInfo, this will be the object whose keys are the identifiers of the novels:
import { State, Action, StateContext, createSelector } from '@ngxs/store';
import { tap } from 'rxjs/operators';
export interface NovelsInfoStateModel {
[key: string]: Novel;
}
export class GetNovelById {
static readonly type = '[Novels info] Get novel by ID';
constructor(public id: string) {}
}
@State<NovelsInfoStateModel>({
name: 'novelsInfo',
defaults: {}
})
export class NovelsInfoState {
static getNovelById(id: string) {
return createSelector(
[NovelsInfoState],
(state: NovelsInfoStateModel) => state[id]
);
}
constructor(private novelsService: NovelsService) {}
@Action(GetNovelById)
getNovelById(ctx: StateContext<NovelsInfoStateModel>, action: GetNovelById) {
const novels = ctx.getState();
const id = action.id;
if (novels[id]) {
// If the novel with ID has been already loaded
// we just break the execution
return;
}
return this.novelsService.getNovelById(id).pipe(
tap(novel => {
ctx.patchState({ [id]: novel });
})
);
}
}
The component, that displays information about the novel, can subscribe to the params observable of the ActivatedRoute to listen to the params change. The code will look as following:
We're using the switchMap in this example, so if the user navigates to another novel and params observable emits new value - we have to complete previously started asynchronous job (in our case it's getting novel by ID).