I have a state that looks something like this:
export interface State {
modules: Module[];
}
And the Module interface is something like:
export interface Module {
name: string;
structure: {
moduleID: string;
icon: string;
...
};
data: [{id: string; value: string; }];
}
The data in the Modules is connected to input fields and combo boxes. When the user changes something in an input field, an action gets dispatched and the store gets updated by the reducer with the new data value for the given data object. I have already verified that the changes happen in the store.
The Reducer is doing the following: ( getModules() just returns the module with that name, and changeElementData() find the element to change and does data.value = value on it)
case fromTitelActions.SET_DATA: {
const stateCopy = {...state};
const moduleToChange = getModule(action.payload.nameOfModule, stateCopy.modules);
action.payload.data.forEach(data => changeElementData(moduleToChange, data.Id, data.value));
return stateCopy;
}
I am trying to subscribe to specific data values and detect changes. My selector looks like this:
export const getDataElementValue = (moduleName, elementId) => createSelector(getModules,
modules => {
const module = modules.find(m => m.name === moduleName);
const data = module.data.find( d => d.id === elementId);
return data.value;
});
Upon subscribing to the selector I get the current value in it, but it never fires again, no matter how many times the reducer updates that particular data object. Any ideas what I am missing? Thanks.
Selectors are basically the NgRx change detection mechanism. When a state is updated, NgRx doesn't do any comparison between old state and the new state to figure out what observables to trigger. It simply sends a new state through all top level selectors.
Among other things one of the features of NgRx selectors is Memoization. They cache the selected part of the application state and return the results quickly.
When should you not use NgRx? Never use NgRx if your application is a small one with just a couple of domains or if you want to deliver something quickly. It comes with a lot of boilerplate code, so in some scenarios it will make your coding more difficult.
The problem is likely because your changeElementData function is not creating new objects when updating the property on moduleToChange.
I would guess you have something like this:
function changeElementData(moduleToChange, id, value) {
moduleToChange.elements.forEach((el) => {
if (el.id == id) el.value = value;
});
}
You would need something like this:
case fromTitelActions.SET_DATA: {
const stateCopy = {...state};
stateCopy.modules = stateCopy.modules.map((moduleToChange) => {
if (module.name != action.payload.nameOfModule) return moduleToChange;
else return action.payload.data.map(data => changeElementData(moduleToChange, data.Id, data.value));
});
return stateCopy;
}
function changeElementData(moduleToChange, id, value) {
let found: boolean = false;
let newModule = module;
if (moduleToChange.elements.find((el) => el.id == id)) {
newModule = {
...moduleToChange,
elements: moduleToChange.elements.map((el) => {
if (el.id == id) return { ...el, value: value };
else return el;
})
}
}
return newModule;
}
When updating a property of an object on the state, you must ALWAYS create a new containing object, because NGRX uses simple object equality to determine if something has changed and fire the relevant observables. If you update a property on an object, the object itself still has the same reference as its previous version, so NGRX will assume it is unchanged.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With