I've been struggling with a problem since yesterday. I don't know if it's a bug or if I'm doing something wrong. Here is my code and to the end my findings. My code is the same from ngrx-example-app (structure etc) and the only difference is that my state is not loaded lazy . Furthermore im using the latest ngrx version v7.3.
export interface State extends EntityState<VehicleClass> {
selectedVehicleClassId: string | null;
}
export const adapter: EntityAdapter<VehicleClass> = createEntityAdapter<VehicleClass>({
selectId: (a: VehicleClass): string => a.uuid,
});
export const initialState: State = adapter.getInitialState({
selectedVehicleClassId: null,
});
export function reducer(state = initialState, action: VehicleClassesActions): State {
switch (action.type) {
case VehicleClassesActionTypes.LOAD_VEHICLE_CLASSES_SUCCESS: {
return adapter.addAll(action.payload, state);
}
default: {
return state;
}
}
}
export const getSelectedVehicleClassId = (state: State) => state.selectedVehicleClassId;
. vehicleFinder
โโโ vehicleClasses
โ โโโ ids: [...,...,..]
โ โโโ entities: [...,...,..]
โ โโโ selectedVehicleClassId: ...
โโโ manufacturer
โ โโโ ids: [...,...,..]
โ โโโ entities: [...,...,..]
โ โโโ selectedManufacturerId: ...
โโโ modelSeries
โ โโโ ids: [...,...,..]
โ โโโ entities: [...,...,..]
โ โโโ selectedVehicleClassId: ...
import * as fromVehicleClasses from './vehicle-classes.reducer';
import { ActionReducerMap } from '@ngrx/store';
export const reducers: ActionReducerMap<any, any> = {
vehicleClasses: fromVehicleClasses.reducer,
manufacturer: fromManufacturer.reducer,
modelSeries: fromModelSeries.reducer,
};
export const {
selectIds,
selectEntities,
selectAll,
selectTotal,
} = fromVehicleClasses.adapter.getSelectors((state: any) => state.vehicleClasses);
StoreModule.forFeature('vehicleFinder', reducers),
When i call fromVehicleClasses.adapter.getSelectors((state: any) => state.vehicleClasses) the state object contains vehicleClasses, manufacturer and modelSeries.
Contrary to my example the ngrx-example-app behaves totally different (it pass the whole global state object) although I don't see any differences to mine except that the corresponding module from ngrx-example-app (books.module.ts) is loaded lazy.
Update: What i forgot to mention is, i have also tried this:
export const selectVehicleFinderState = createFeatureSelector('vehicleFinder');
export const getVehicleClassesEntitiesState = createSelector(
selectVehicleFinderState,
state => state.vehicleClasses
);
// @note: this produce an error:
// ERROR TypeError: Cannot read property 'vehicleClasses' of undefined
const result = fromVehicleClasses.adapter.getSelectors(getVehicleClassesEntitiesState);
StoreModule.forFeature('books', reducers),
. books
โโโ search
โ โโโ ...
โโโ books
โ โโโ ids: [...,...,..]
โ โโโ entities: [...,...,..]
โ โโโ selectedBookId: ...
โโโ collection
โ โโโ ...
https://github.com/ngrx/platform/blob/master/projects/example-app/src/app/books/books.module.ts#L26-L32
https://github.com/ngrx/platform/blob/master/projects/example-app/src/app/books/reducers/index.ts
The call on fromBooks.adapter.getSelectors(state => console.log(state)) inside of the ngrx-example-app is logging the whole global state (see below):
. books
โโโ search
โ โโโ ...
โโโ books
โ โโโ ids: [...,...,..]
โ โโโ entities: [...,...,..]
โ โโโ selectedBookId: ...
โโโ collection
โ โโโ ...
Why adapter.getSelectors behaves different? In the ngrx-example-app the argument of the callback function which is called by getSelectors contains the whole global state object and in my app it just contain a slice from a state? What im doing wrong?
The example app has a need for createFeatureSelector but mine needs none because i can not get from top level state because its get some slice state? Why is that so?.
How does the adapter.getSelectors(callbackFunc) work? Will the callbackFunc pass the global state object when calling getSelectors? Why isn't my case like this?
FYI: Unfortunately I cannot provide a repo for moment. I hope this will be enough for now (maybe i can provide later).
Can it be that adapter.getSelectors behaves differently with lazy loaded modules? So that it passes the global state object?
@SerkanSipahi IMO the best way to get help is via a complete sample in a StackBlitz or in a GitHub repository,
Thanks @manklu. We already talked to @SerkanSipahi about opening this issue without a full repro yet.
Hmmm, I don't see it directly...
Just to be sure, you did register ngrx/store with StoreModule.forRoot in the AppModule right?
(I've updated the question to add some TS formatting)
@timdeschryver sure.
StoreModule.forRoot({}),
Im having this same issue i Know its not a bug but im missing something in my understanding of working with ngrx enity
Will you show how you are using the selector also? store.pipe(select(...))
export interface State {
vehicleFinder: VehicleFinderState;
}
export interface VehicleFinderState {
vehicleClasses: fromVehicleClasses.State;
}
export const reducers: ActionReducerMap<VehicleFinderState> = {
vehicleClasses: fromVehicleClasses.reducer,
};
export const selectVehicleFinderState = createFeatureSelector<State, VehicleFinderState>(
'vehicleFinder'
);
export const getVehicleClassesEntitiesState = createSelector(
selectVehicleFinderState,
state => state.vehicleClasses
);
export const { selectAll } = fromVehicleClasses.adapter.getSelectors(getVehicleClassesEntitiesState);
export const selectAllVehicleClasses = createSelector(selectVehicleFinderState, selectAll);
// somewhere in the component
...
...
vehicleClasses$: Observable<VehicleClass[]> = this.store.select(selectAllVehicleClasses);
...
...
I think something goes wrong between createFeatureSelector and this.store.select(selectAllVehicleClasses); but I can't exactly localize it!
the compiler also fails.
Error:(56, 55) TS2345: Argument of type 'MemoizedSelector<State, VehicleFinderState>' is not assignable to parameter of type '[SelectorWithProps<{}, {}, State>, SelectorWithProps<{}, {}, {}>, SelectorWithProps<{}, {}, {}>, SelectorWithProps<{}, {}, {}>, SelectorWithProps<{}, {}, {}>, SelectorWithProps<...>, SelectorWithProps<...>, SelectorWithProps<...>]'.
Type 'MemoizedSelector<State, VehicleFinderState>' is missing the following properties from type '[SelectorWithProps<{}, {}, State>, SelectorWithProps<{}, {}, {}>, SelectorWithProps<{}, {}, {}>, SelectorWithProps<{}, {}, {}>, SelectorWithProps<{}, {}, {}>, SelectorWithProps<...>, SelectorWithProps<...>, SelectorWithProps<...>]': 0, 1, 2, 3, and 33 more.
@brandonroberts i believe you are right with this.store.select(selectAllVehicleClasses);
Yep. The lines that jump out to me are
export const { selectAll } = fromVehicleClasses.adapter.getSelectors(getVehicleClassesEntitiesState);
export const selectAllVehicleClasses = createSelector(selectVehicleFinderState, selectAll);
Your selectAllVehicleClasses is filtering down twice from (root -> all vehicle classes) -> all vehicle classes. Your selector should look something like
export const { selectAll } = fromVehicleClasses.adapter.getSelectors();
export const selectAllVehicleClasses = createSelector(selectVehicleFinderState, selectAll);
vehicleClasses$: Observable<VehicleClass[]> = this.store.select(selectAllVehicleClasses);
OR
export const { selectAll: selectAllVehicleClasses } = fromVehicleClasses.adapter.getSelectors(getVehicleClassesEntitiesState);
vehicleClasses$: Observable<VehicleClass[]> = this.store.select(selectAllVehicleClasses);
When you pass a function to the adapter#getSelectors, you're saying "I'm providing you a selector to get to my entities". The selectAll in this case is a function that returns your ids mapped to your entities. You don't need to define an additional selector.
When you don't pass a function to getSelectors(), you're saying "Just give me a function I will use to compose with another selector"
Thank you, it looks good, the previous mistake will not become more but now i have an other error:
core.js:15723 ERROR TypeError: Cannot read property 'map' of undefined
at entity.js:28
at store.js:635
at memoized (store.js:577)
at defaultStateFn (store.js:604)
at store.js:643
at memoized (store.js:577)
at MapSubscriber.project (store.js:520)
at MapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/map.js.MapSubscriber._next (map.js:35)
at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (Subscriber.js:53)
at MapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/map.js.MapSubscriber._next (map.js:41)
and a compiler error in vehicleClasses$: Observable<VehicleClass[]> = this.store.select(selectAllVehicleClasses);
Error:(18, 67) TS2345: Argument of type '(state: EntityState<VehicleClass>) => VehicleClass[]' is not assignable to parameter of type '"vehicleClasses"'.
I had this map error yesterday (i think i was on a good track but then throw it later because it not working proper for me).
Try this
export const selectVehicleFinderState = createFeatureSelector<State, VehicleFinderState>(
'vehicleFinder' // feature root
);
export const getVehicleClassesEntitiesState = createSelector(
selectVehicleFinderState,
state => state.vehicleClasses // vehicleFinder -> vehicleClasses (ids, entities, selectedVehicleClassId)
);
export const { selectAll } = fromVehicleClasses.adapter.getSelectors(); // select all (map ids to entities)
export const selectAllVehicleClasses = createSelector(getVehicleClassesEntitiesState, selectAll); // vehicleFinder -> vehicleClasses (ids, entities, selectedVehicleClassId) -> selectAll
vehicleClasses$: Observable<VehicleClass[]> = this.store.select(selectAllVehicleClasses);
Yes, its works (renders correct values, no runtime error). Thank you so much. I am infinitely grateful. Now i have to process (brain) the whole thing first :) That was really a lot in the last 2 days for me. Had a lot of pain!
Last think, i get a compiler error for vehicleClasses$: Observable<VehicleClass[]> = this.store.select(selectAllVehicleClasses); compiler error ==> selectAllVehicleClasses <==
The compiler error has no effect when its runs in runtime.
Error:(18, 67) TS2345: Argument of type 'MemoizedSelector<State, VehicleClass[]>' is not assignable to parameter of type '"vehicleClasses"'.
@brandonroberts I think we can close this issue ticket because my last thread has nothing to do with with the initial error (i guess).
Thank you again.
Cool. Yea, I think the other error was due to the old selector. Glad it got sorted out.
Most helpful comment
Cool. Yea, I think the other error was due to the old selector. Glad it got sorted out.