REPO: https://github.com/morningharwood/platform/tree/feature/ngrx-firebase
When I try to subscribe to my selectAll of my post ngrx entity feature I get the error:
Cannot read property of map of undefined
It has to be a simple typo I'm following tutorials verbatim.
Weird part is if I use string selector:
this.selected$ = this.store.select('post');
this.selected$.subscribe(console.log); // no error here.
I get a no error.
Question:
When using ngrx entity, How can I get my selectAll
selector to log out the all posts from ngrx store collection post
?
My post.reducer.ts
import {
PostActions,
PostActionTypes,
} from './post.actions';
import {
ActionReducerMap,
createFeatureSelector,
} from '@ngrx/store';
import {
createEntityAdapter,
EntityState,
} from '@ngrx/entity';
import { Post } from './post.interfaces';
export const postAdapter = createEntityAdapter<Post>();
export interface PostState extends EntityState<Post> {}
export const postInitialState: PostState = {
ids: [ '1234' ],
entities: {
'1234': {
id: '1234',
header: {
title: 'title 1',
subtitle: 'subtitle 1',
},
body: {
sections: [],
},
},
},
};
export const initialState: PostState = postAdapter.getInitialState(postInitialState);
export function postReducer(state: PostState = initialState, action: PostActions): PostState {
switch (action.type) {
case PostActionTypes.ADD_ONE:
return postAdapter.addOne(action.post, state);
default:
return state;
}
}
export const getPostState = createFeatureSelector<PostState>('post');
export const {
selectIds,
selectEntities,
selectAll: selectAllPosts,
selectTotal,
} = postAdapter.getSelectors(getPostState);
export const postReducersMap: ActionReducerMap<any> = {
post: postReducer,
};
And app.component
:
import {
Component,
OnInit,
} from '@angular/core';
import { AppState } from './app.interfaces';
import { Store } from '@ngrx/store';
import { selectAllPosts } from './post/post.reducer';
import { Observable } from 'rxjs/Observable';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ],
})
export class AppComponent implements OnInit {
public selected$: Observable<any>;
constructor(private store: Store<AppState>) {
}
ngOnInit() {
this.selected$ = this.store.select(selectAllPosts);
this.selected$.subscribe(console.log); // error here
}
}
My forFeature
and forRoot
modules:
@NgModule({
imports: [
BrowserModule.withServerTransition({ appId: 'portfolio-app' }),
StoreModule.forRoot({ reducers }),
EffectsModule.forRoot([]),
PostModule,
],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ],
providers: [],
})
export class AppModule {
}
import { StoreModule } from '@ngrx/store';
import { postReducers } from './post.reducer';
@NgModule({
exports: [],
declarations: [],
imports: [StoreModule.forFeature('post', postReducersMap)],
})
export class PostModule {
}
For people who come across this in the future this can be solved by refactoring the selectors slightly.
As per the (somewhat incomplete at the time of posting) docs here, ngrx.io/entity/adapter, you need to select the featureSate using the createFeatureSelector
function and then use that substate with the adapter selectors to select slices of that state.
Change this:
post.reducer.ts
export const getPostState = createFeatureSelector<PostState>('post');
export const {
selectIds,
selectEntities,
selectAll: selectAllPosts,
selectTotal,
} = postAdapter.getSelectors(getPostState);
to this:
export const selectPostState = createFeatureSelector<PostState>('post');
export const {
selectIds,
selectEntities,
selectAll
selectTotal,
} = postAdapter.getSelectors();
export const selectPostIds = createSelector(
selectPostState,
selectIds
);
export const selectPostEntities = createSelector(
selectPostState,
selectEntities
);
export const selectAllPosts = createSelector(
selectPostState,
selectAll
);
This will select the substate of the postState
feature.
Hope this helps
Edit: So it appears the above two examples are equivalent and the issues I was facing was the same as Matthew Harwood and it was improper use of the ActionReducerMap
.
I think the docs might be a bit confusing on this as they lead to believe that the state of and entity is the same thing as the state of a feature state such as a lazy loaded state.
While an entity state has properties such as id's and entities, they do not need a reduce each like feature state does but only one reducer to cover all the entity state for that model.
e.g.
export const initialState: PostState = postAdapter.getInitialState(postInitialState);
does not need a reducer map for the PostAdapterState
because the one reducer
export function postReducer(state: PostState = initialState, action:
PostActions): PostState {
switch (action.type) {
case PostActionTypes.ADD_ONE:
return postAdapter.addOne(action.post, state);
default:
return state;
}
}
Covers all the entity state updating.
You would need a reducer map if you multiple adapter states in a feature state such as the example below.
Lets say you had a Library feature state that and entities of books and magazines. It would look something like the below.
book.reducer.ts
export interface BookEntityState extends EntityState<Book> {
selectedBookId: string | null;
}
const bookAdapter: EntityAdapter<Book> = createEntityAdapter<Book>();
export const initialBookState: BookEntityState = adapter.getInitialState({
selectedBookId: null
});
export const bookReducer (...) ... etc
magazine.reducer.ts
export interface MagazineEntityState extends EntityState<Magazine> {
selectedMagazineId: string | null;
}
const magazineAdapter: EntityAdapter<Magazine> = createEntityAdapter<Magazine>();
export const initialMagazineState: MagazineEntityState =
adapter.getInitialState({
selectedMagazineId: null
});
export const magazineReducer (...) ... etc
And then you library feature state may be something like
library.reducer.ts
// Feature state,
export interface LibraryFeatureState {
books: BookEntityState
magazines: MagazineEntityState
}
// Reducer map of the lirbray
export const libraryReducersMap: ActionReducerMap<LibraryFeatureState> = {
books: bookReducer,
magazines: magazineReducer
};
And then adding the feature state to your Store in the LibraryModule imports array.
library.module.ts
...
StoreModule.forFeature<LibraryFeatureState>('libraryState',
libraryReducersMap),
...
Hope this helps someone else.
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