Search code examples
angularngrxngrx-storengrx-effectsngrx-entity

@ngrx/entitiy selector doesn't fire when changing custom state


I have expanded a @ngrx/entity state store to include a loading value for the entities that should be retrieved from the server.

const adapter = createEntityAdapter<A>();
export interface AState extends EntityState<A> {
  loading: {
    projects: {
      [id: string]: boolean
    },
    collections: {
      [id: string]: boolean
    }
  };
}
const initialState: AState = adapter.getInitialState({
  loading: {
    projects: {},
    collections: {}
  }
});

To be able to display this loading value I use this selector:

export const getRunsLoadingByProject = createSelector(
  (state: AppState) => state.a,
  (state: AState, Id: number) => {
    return !!state.loading.projects[Id];
  }
);

This works great on the first load. The enities and the loading values get updated and the selector works like a charm. The problem happens with a 'update' button I need on the site. As soon as the entity state coming from the server is the same as the state already in the store the selector stops retrieving the new loading states. Using the devtools I can see that the state gets changed in the right way (loading flag gets set to true then false).

It just seems to be the selector. Is this a quirk of @ngrx/entities that the selector only fires when the entities change? Or am I missing something?

Edit: Reducer

export function aReducer(state: AState = initialState, action: AEffectsActions): RunState {
  switch (action.type) {
    case AEffectsActionTypes.LOAD_RUN: {
      const newState = { ...state };
      newState.loading.projects[action.payload.toString()] = true;
      return newState;
    }
    case AEffectsActionTypes.LOAD_RUN_SUCCESS: {
      const newState = adapter.addMany(action.runs, state);
      newState.loading.projects[action.projectId] = false;
      return newState;
    }
    default:
      return state;
  }

} 

Solution

  • The spread operator only "clones" at the top level, your selector isn't being executed because the reference to loading.projects is still the same.

    So you'll have to do something like the following.

    return {
       ...state,
       loading: {
          ...state.loading,
          projects: {
            ...state.loading.projects,
            [action.payload.toString()]: true
          }
    
       }
    };