Issue
I have a "feature" state in my NxRx (v7) enabled application, that looks like the following..
State
export interface DataPageState {
dataReceivedTimestamp: number;
formData: PageSaveDataState; // <--- setting this
previewDataHeaderNames: string[] // } <--- calls selector for these
previewData: string[][]; // }
error: string;
isLoading: boolean;
}
Selectors
I have a selector to get both previewDataHeaderNames
and previewData
– returned as an object as I use them to populate a data grid
export const getDataPreviewFeatureState = createFeatureSelector<DataPreviewPageState>(NgRxStateNames.dataPreviewPage);
/** Get preview data */
export const getPreviewData = createSelector(
getDataPreviewFeatureState,
(state: DataPreviewPageState) => {
return { previewData: state.previewData, headers: state.previewDataHeaderNames };
}
);
Reducer
In my reducer, I have the following to update the formData
slice of state. I Have a form on the same page and I use this to save its state (as well as data grid layout etc)
case myActions.ActionTypes.SetPreviewFormData: {
this.logger.info('SetPreviewFormData');
const newState = { ...action.payload, timeStamp: this.timeService.getUtcNowTimestamp() }
return { ...state, formData: newState };
}
Selector firing when I set the formData
Finally, in my component code, I have a selector for the getPreviewData
this.subs.sink = this.store$.select(fromDataPreview.getPreviewData).subscribe(previewData => {
this.loadGridData(previewData.headers, previewData.previewData);
});
When I route to another page, just before I route I call an action to set the form data as per the case myActions.ActionTypes.SetPreviewFormData
above.
But when I call this, I have the above selector fire, and so my method to set the (already set) grid data is called again. If I comment out my dispatch to myActions.ActionTypes.SetPreviewFormData
this is behaviour no longer happens.
My question is why does setting one slice of the state fire a select different slice? From what I understand the whole idea of the selectors was to only get the notification when the property involved in that selector, otherwise may as well just have one selector that returns the whole state.
Any idea why this is happening, or am I wrong in my assumption?
Thanks in advance!
Solution
I would have to double check, but my assumption would be:
Your reducer returns a complete new object for the state thru the spreading syntax (which is correct):
return { previewData: state.previewData, headers: state.previewDataHeaderNames };
Now, the selectors cache resets and all selectors are recomputed.
See e.g. https://medium.com/swlh/memoization-with-selectors-in-ngrx-c60e67a08161, explains it rather well imo.
I think you might achieve what you want with the following pattern:
First, define two selectors for the two single state attributes you require:
export const getPreviewData = createSelector(
getDataPreviewFeatureState,
(state: DataPreviewPageState) => state.previewData
}
);
export const getPreviewDataHeaderNames = createSelector(
getDataPreviewFeatureState,
(state: DataPreviewPageState) => state.previewDataHeaderNames
}
);
Then combine these two selectors in order to define the selector for your component:
export const getPreviewDataWithNames = createSelector(
getPreviewData,
getPreviewDataHeaderNames,
(previewData, previewDataHeaderNames) => ({ previewData, previewDataHeaderNames })
);
Now the following should occur: Since the references for previewData and previewDataHeaderNames do not change and the selector has no direct reference to your state, your combined selector does not recompute.