I've encountered TypeErrors while using NgRx select functions to access nested properties.
In my app.module.ts
, I have configured the root store as follows:
StoreModule.forRoot({ app: appReducer }),
The selectors causing errors are related to some nested properties:
const getAppFeatureState = createFeatureSelector<IAppState>('app');
export const getAppConfig = createSelector(getAppFeatureState, state => {
return state.appConfig.data;
});
export const getConfigControls = createSelector(getAppConfig, state => {
console.log({ state }) // logs values from initial state
return state.controls;
});
export const getConfigDropdowns = createSelector(
getConfigControls,
state => state.dropdowns,
);
When subscribing to these selectors in app.compontent.ts
like so:
ngOnInit() {
this.store.dispatch(new appActions.LoadAppConfig());
this.store
.pipe(select(appSelectors.getConfigDropdowns))
.subscribe(data => {
console.log('OnInit Dropdowns Data: ', data);
});
}
app.component.ts:31 ERROR TypeError: Cannot read property 'dropdowns' of null at app.selectors.ts:18
Adding logging revealed that only the initialState
values were being logged. This leads me to believe that the selector function should not fire until the value changes. However, since it doesn't, the error occurs when trying to access a property on null. Does the initialState
need to include all potential future nested properties for the selectors to work correctly?
How can I prevent the selector from firing when its value remains unchanged?
Furthermore, is the configuration of StoreModule.forRoot
correct? It seems unusual to me that creating a "root" store places the app
key parallel to module stores, rather than underneath it.
Edit:
I'll also provide an overview of the structure in app.reducer.ts
. I use immer to simplify updating nested properties, although I have tested the reducer without immer with similar results.
import produce from 'immer';
export const appReducer = produce(
(
draftState: rootStateModels.IAppState = initialState,
action: AppActions,
) => {
switch (action.type) {
case AppActionTypes.LoadAppConfig: {
draftState.appConfig.meta.isLoading = true;
break;
}
/* more cases updating the properties accessed in problematic selectors */
default: {
return draftState;
}
}
}
Edit: Add initialState
:
const initialState: rootStateModels.IAppState = {
user: null,
appConfig: {
meta: {isError: false, isLoading: false, isSuccess: false},
data: {
controls: {
dropdowns: null,
}
},
},
};