I am currently working with a somewhat complicated (deep) structure within an ngrx project. It can be thought of as an array of parent objects, with multiple levels of child objects. It is normalized/flattened on the server side, and my the feature within my store looks something like this:
rootObjs: {
level1: {
byId: {
'lvl1_1': {id: 'lvl1_1', label: '[Lvl 1]: 1', ui_open: true, children: ['lvl2_1', 'lvl2_3']},
'lvl1_2': {id: 'lvl1_2', label: '[Lvl 1]: 2', ui_open: false, children: ['lvl2_2']}
},
allIds: [
'lvl1_1', 'lvl1_2'
]
},
level2: {
byId: {
'lvl2_1': {id: 'lvl2_1', label: '[Lvl 2]: 1', ui_open: false, children: ['lvl3_1', 'lvl3_2']},
'lvl2_2': {id: 'lvl2_1', label: '[Lvl 2]: 2', ui_open: true, children: ['lvl3_3']},
'lvl2_3': {id: 'lvl2_1', label: '[Lvl 2]: 3', ui_open: false, children: []}
},
allIds: [
'lvl2_1', 'lvl2_2', 'lvl2_3'
]
},
level3: {
byId: {
'lvl3_1': {id: 'lvl3_1', label: '[Lvl 3]: 1', ui_open: false,},
'lvl3_2': {id: 'lvl3_2', label: '[Lvl 3]: 2', ui_open: false,},
'lvl3_3': {id: 'lvl3_3', label: '[Lvl 3]: 3', ui_open: false,},
}
allIds: [
'lvl3_1', 'lvl3_2', 'lvl3_3'
]
}
}
Now I am trying to write my selectors. My issue is that all objects need to be displayed on the screen at once, however they must all be editable separately. Thus, I am trying to create a selector that allows me to select each component individually- something like:
export const rootObjFeature = createFeatureSelector<RootObj>('rootObjs');
export const selectLevel1 = (id: string) => createSelector(
rootObjFeature, (state: JobPlanner) => {
// Grab only the level2 children associated with selected level1
const lvl2 = state.level1.byId[id].children.map(key => state.level2.byId[key]);
// Grab only the level3 children of level2 associated with selected level1
const lvl3 = [].concat(
...state.lvl2.map( l2 => l2.children.map(key => state.level3.byId[key]));
);
return {
...state.level1.byId[id],
level2: lvl2,
level3: lvl3
};
}
);
Then in my Level1Component init, I do something like this:
export class Level1Component implements OnInit, OnDestroy {
@Input() id: string;
lvl1Sub: Subscription;
lvl1: Level1Model;
constructor(private store: Store<AppState>) { }
ngOnInit() {
this.lvl1Sub = this.store.select(selectLevel1(this.id)).subscribe(l1 => {
console.log('loading level 1: '+this.id);
this.lvl1 = l1;
});
}
ngOnDestroy() {
this.lvl1Sub.unsubscribe();
}
}
With this setup I can pass the proper level2 and level3 objects on to their own components (where those children can be opened, closed, edited, etc..). HOWEVER, due to how I have my selector, any time ANY level1, level2, or level3 item is edited (e.g.- ui_open is toggled for lvl1_1), EVERY level1 component's lvl1Sub method is called. This is an issue as my view might have hundreds of level1 components, but only one will be edited at a time. Is there a way to set up a selector that will only call its subscription when just those store elements associated with a single ID is changed?