I need to access the state of a ngrx/data entity in a reducer function.
I'm building a pager (pagination) where the user can navigate to the last page among other options. But I don't know how many items there are in the cache, the EntitySelectorsFactory
does (selectCount
), but I'd not like to get the data in the component and use props to pass them, instead they should be in the reducer.
Or maybe there's a better way.
Actions
import {createAction, props} from '@ngrx/store';
export const toStart = createAction('[Pager] To Start');
export const toEnd = createAction('[Pager] To End');
export const incrementPage = createAction('[Pager] Increment');
export const decrementPage = createAction('[Pager] Decrement');
export const setPage = createAction('[Pager] Set', props<{page: number}>());
export const setPageSize = createAction('[Pager] Set Page Size', props<{size: number}>());
Reducers
import { Action, createReducer, on } from '@ngrx/store';
import * as PagerActions from '../actions/pager';
import {EntitySelectorsFactory} from '@ngrx/data';
import {Rant} from '../models/rant';
export const pagerFeatureKey = 'pager';
export interface State {
page: number;
pageSize: number;
}
export const initialState: State = {
page: 1,
pageSize: 10,
};
const rantSelectors = new EntitySelectorsFactory().create<Rant>('Rant');
const pagerReducer = createReducer(
initialState,
on(PagerActions.toStart, state => ({...state, page: 1})),
on(PagerActions.toEnd, state => ({...state, page: Math.floor(rantSelectors.selectEntities.length / state.pageSize)})),
on(PagerActions.setPage, (state, action) => ({...state, page: action.page})),
on(PagerActions.incrementPage, state => ({...state, page: state.page + 1})), // TODO: check if out of bounds
on(PagerActions.decrementPage, state => ({...state, page: state.page > 1 ? state.page - 1 : 1})),
on(PagerActions.setPageSize, (state, action) => ({...state, pageSize: action.size}))
);
export function reducer(state: State | undefined, action: Action) {
return pagerReducer(state, action);
}
No matter what I do in
on(PagerActions.toEnd, state => ({...state, page: Math.floor(rantSelectors.selectEntities.length / state.pageSize)})),
it's always 0. rantSelectors.selectCount.length
or the above. Probably because the length is 0.
I'll also need to know the length or count of the "rant" entities in the increment
reducer.
I don't know how to get the actual value.
I'm thinking... have a selector for the entity count and use props to pass that count in the toEnd
action. Another option would be a side effect, but they're confusing and imho are not the proper way to write code (unreadable code, immediate cause/effect missing, yes I know, I don't want to argue about it). And then even if creating a side effect how would one define it in the module? Would it be an AppEffects
side effect or a distinct side effect? I'm guessing AppEffects because it's not in another module.
app.module.ts
EffectsModule.forRoot([AppEffects]),
AppEffects = pretty much empty, default ng add
contents.
So yeah, I need to get the state of a @ngrx/data entity in the reducer, get its actual value, or a more elegant approach.
I prefer to keep my components clean... I see two solutions to your problem
rantSelectors.selectEntities.length
in this part of the store, by adding a number value to your state. When you load/update the rantSelectors.selectEntities
you then also update this slice of the store.export interface State {
page: number;
pageSize: number;
rantEntitiesSize: number;
// ...
on(rantActions.loadSuccess,
rantActions.UpdateSuccess, //could include adding objects to the current loaded list
rantActions.RemoveSuccess, //(negative number)
(state, action) =>
({...state, rantEntitiesSize: (state.rantEntitiesSize + action)}),
}
toEnd action
and not use that in the reducer, instead build an effect which is called on toEnd action
that gets the values you need from the other part of the store and then calls a second action that passes the value to the reducer. toEndPager$ = createEffect(() =>
this.actions$.pipe(
ofType(PagerActions.toEnd),
withLatestFrom(this.store.select(rantSelectors.selectEntities.length)),
switchMap(([{},length]) => {
return of(PagerActions.toEndSuccess({ length });
})
)
);