1
votes

I'm trying to create a library which has a feature-state and a combined reducer.

My feature reducer looks like this:

index.ts

export const storageFeatureKey = 'storage';

export interface StorageState {
  [fromBookEntries.bookEntriesFeatureKey]: fromBookEntries.State
}

export function reducers(state: StorageState | undefined, action: Action) {
  return combineReducers({
    [fromBookEntries.bookEntriesFeatureKey]: fromBookEntries.reducer
  })(state, action);
}

bookEntriesFeatures looks like this:

book-entries.reducer.ts

export const bookEntriesFeatureKey = 'book-entries';

export interface State extends EntityState<WarehouseBookEntry> {
  selectedEntryId: number;
}

export const adapter: EntityAdapter<WarehouseBookEntry> = createEntityAdapter<WarehouseBookEntry>({
  selectId: (entry) => entry.id
});

export const initialState: State = adapter.getInitialState({
  selectedEntryId: null
});

export const reducer = createReducer(
  initialState,
  on(...),
  on(...)
);

export const getSelectedEntryId = (state: State) => state.selectedEntryId;

const {
  selectIds,
  selectEntities,
  selectAll,
  selectTotal
} = adapter.getSelectors();

export const getBookEntriesIds = selectIds;
export const getBookEntriesEntities = selectEntities;
export const getAllBookEntries = selectAll;
export const getTotalBookEntries = selectTotal;

In my feature module I import StoreModule.forFeature() with my feature key and the feature reducer.

No I'm trying to select an entry based on a selected id. Therefore i fire an action, which successfully adding the id of the selected entry to my book-entries state.

To select the selected entry I have following selector inside my feature selectors:

index.ts

export const selectSelectedBookEntry = createSelector(
  fromBookEntries.getBookEntriesEntities, // fromBookEntries is in my bookEntriesFeature
  selectSelectedBookEntryId, // simply returns the selected id
  (entries, selectedId) => {
    //console.log(entries, selectedId)
    //console.log("fired");
    return entries && entries[selectedId];
  }
);

When loading entries via another dispatched action, the selector is only called onces (before the data loaded). I found out that when I create the selectors from my bookEntriesAdapter inside my feature selectors, it works. Like this:

index.ts

export const selectStorageState = createFeatureSelector<StorageState>(
  storageFeatureKey
);

export const selectBookEntriesState = createSelector(
  selectStorageState,
  state => state[fromBookEntries.bookEntriesFeatureKey]
);

const {
  selectIds,
  selectEntities: selectBookEntriesEntities,
  selectAll,
  selectTotal
} = fromBookEntries.adapter.getSelectors(selectBookEntriesState);

export const selectAllBookEntries = createSelector(
  selectBookEntriesState,
  selectAll
)

export const selectSelectedBookEntry = createSelector(
  selectBookEntriesEntities,
  selectSelectedBookEntryId,
  (entries, selectedId) => {
    //console.log(entries, selectedId)
    //console.log("fired");
    return entries && entries[selectedId];
  }
);

But why? I'm a little lost trying to understand why this does not work the other way around.

The above example that does not work is from the official ngrx doc (https://ngrx.io/guide/entity/adapter). The one thats working is from ngrx/platform example code (https://github.com/ngrx/platform/blob/master/projects/example-app/src/app/books/reducers/index.ts).

I don't think it's necessary, but I do not extend the root-app-store-state since I have no access to it, because it's a library (Trying to get around domain-driven-design and I created a feature-lib to handle this kind of problem). Or might this be the problem?

I would appreciate it if someone could explain this a little more.

1
please share code of your selectors.satanTime

1 Answers

2
votes

In your code you miss createFeatureSelector and its integration.

It's important to select your feature state first from the global state and then select data you need.

when you use selectors like that

const {
  selectIds,
  selectEntities,
  selectAll,
  selectTotal
} = adapter.getSelectors();

export const getBookEntriesEntities = selectEntities;

they know nothing about StoreModule.forFeature(FEATURE_KEY) and it's your responsibility to narrow global state for them.

For that you need createFeatureSelector, it selects your feature from the global state.

Therefore to use getBookEntriesEntities you need first to select your feature and then to pass this data into getBookEntriesEntities.

const selectMyFeature = createFeatureSelector(FEATURE_KEY);

const selectMyFeatureEntities = createSelector(
  selectMyFeature,
  getBookEntriesEntities,
);

now when we use store.select(selectMyFeatureEntities) it knows how to select data from the feature.

The right code in your case would be


const {
  selectIds,
  selectEntities,
  selectAll,
  selectTotal
} = adapter.getSelectors();

// put here feature name from .forFeature
const selectMyFeature = createFeatureSelector(FEATURE_KEY);

export const selectMyFeatureBooks = createSelector(
  selectMyFeature,
  state => state[fromBookEntries.bookEntriesFeatureKey]
);

export const getSelectedEntryId = createSelector(
  selectMyFeatureBooks,
  (state: State) => state.selectedEntryId,
);

export const getBookEntriesIds = createSelector(
  selectMyFeatureBooks,
  selectIds,
);
export const getBookEntriesEntities = createSelector(
  selectMyFeatureBooks,
  selectEntities,
);
export const getAllBookEntries = createSelector(
  selectMyFeatureBooks,
  selectAll,
);
export const getTotalBookEntries = createSelector(
  selectMyFeatureBooks,
  selectTotal
);

and now selectSelectedBookEntry will work as expected.