Using custom selectors for one-to-one or one-to-many data relationships.
by Kate Sky
When implementing state management with Ngrx we often have a predefined API that can not be changed. I ran across a business case: back end API is written in a way where 2 sets of data that are returned are depend on the other. One-to-one or one-to-many relationship. In order to map entities with corresponding ids we have to create a custom selector.
To make a selector that will do a lookup of another selector just add 4 Actions, 2 Effects and 3 selectors:
This is a declaration of the 2 Actions
export class LoadStudents implements Action{
readonly type = ActionTypes.LoadStudents;
}
export class LoadTeachers implements Action{
readonly type = ActionTypes.LoadTeachers;
}
export class Students implements Action{
readonly type = ActionTypes.Students ;
constructor(public payload: any){}
}
export class Teachers implements Action{
readonly type = ActionTypes.Teachers;
constructor(public payload: any){}
}
export type ActionUnion = LoadStudents| LoadTeachers
| Students | Teachers ;
This is an implementation of the 2 effects
@Injectable()
export class Effects {
constructor(private actions$: Actions, private service: myService ) {
}
@Effect()
loadStudents$ = this.actions$.pipe(
ofType<appActions.LoadStudents>(appActions.ActionTypes.LoadStudents),
switchMap(() => this.service.getStudents().pipe(
map((data) =>
new appActions.Students(data)
))));
@Effect()
loadTeachers$ = this.actions$.pipe(
ofType<appActions.LoadTeachers >(appActions.ActionTypes.LoadTeachers ),
switchMap(() => this.service.getTeachers().pipe(
map((data) =>
new appActions.Teachers(data)
))));
}
When declaring selectors you have to remember that order of loaded data is not predictable and we have to account for it not all being loaded yet:
export interface State {
csaApp: fromFeatures.State
}
export const reducers:ActionReducerMap<State>= {
csaApp: fromFeatures.filteredAppReducer
}
const getAppState = createFeatureSelector<fromFeatures.State>('csaApp');
export const getStudents = createSelector(getAppState, (state)=>state.students);
export const getTeachers= createSelector(getAppState, (state)=>state.teachers);
export const getStudentsWithTeachers = createSelector(getStudents, getTeachers,(students, teachers) => {
if(students === null || teachers === null) return null;
students.map(s=>s.teacher = teachers.find(t=>t.id ===s.teacherId));
return students;
})
Statement management using Ngrx is a huge subject and creating custom selector will be a great addition to any angular project.
See my github repository for a work-in-progress project I am workig on to support elementary school events for my children’s’ school. https://github.com/katesky/csa-school-app
To see the working (wip) site visit: https://katesky.github.io/csa-school-app