I'm currently grappling with the concept of "entity arrays" in my ngrx Store.
Let's say I have a collection of PlanDTO
retrieved from my api server. Based on the research I've done, it seems necessary to set up a kind of "table" to store them:
export interface IPlan {
id?: string;
name?: string;
...
}
export interface IPlanRedux {
byId: { [key: string]: IPlan };
allIds: Array<string>;
}
When a 'LOAD_PLANS'
action is dispatched, an effect is triggered:
@Injectable()
export class PlanEffects {
constructor(
private actions$: Actions,
private store$: Store<IStore>,
private planService: PlansService,
) { }
@Effect({ dispatch: true })
loadPlans$: Observable<Action> = this.actions$
.ofType('LOAD_PLANS')
.switchMap((action: Action) =>
this.planService.list()
.map((plans: Array<PlanDTO>) => {
return { type: 'LOAD_PLANS_SUCCESS', payload: plans };
})
.catch((err: ApiError) => {
return Observable.of({ type: 'LOAD_PLANS_FAILED', payload: { code: err.code, msg: err.message } });
})
);
}
If everything goes smoothly, another action, 'LOAD_PLANS_SUCCESS'
, is dispatched. The reducer for this action looks like this:
private static loadPlansSuccess(plansRdx: IPlanRedux, type, payload: Array<PlanDTO>) {
const plans = payload;
const newPlans = plans.filter(plan => !plansRdx.byId[plan.id]);
const newPlanIds = newPlans.map(plan => plan.id);
const newPlanEntities = plans.reduce((entities: { [id: string]: IPlan }, plan: IPlan) => {
return Object.assign(entities, {
[plan.id]: plan
});
},
{});
return {
ids: [ ...plansRdx.allIds, ...newPlanIds ],
byId: Object.assign({}, plansRdx.byId, newPlanEntities),
};
}
Everything appears to be functioning correctly, but there are two key concepts that I need to grasp here:
- Internal table structure for storing redux data.
- Front-end components integration.
The main challenge I am facing is dealing with an Observable<IPlanRedux>
instead of an Observable<IPlan>
within my components. While internally managing the IPlanRedux
table, I also want to indicate which plans have been removed and added.
I hope I've articulated my thoughts clearly.
EDIT
I attempted to use selectors:
export interface IPlanRedux {
entities: { [key: string]: IPlan };
ids: Array<string>;
}
export const getPlans = (planRdx: IPlanRedux) => planRdx.entities;
const getPlansState = (state: IStore) => state.plans;
export const getPlanEntities = createSelector(getPlansState, getPlans);
In my component:
this.plans$ = this.store$.select(fromRoot.getPlanEntities)
.map(plan => {
if (plan.id != null)