Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot read property of map of undefined when using featureSelector ngrx

Tags:

angular

ngrx

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 forFeatureand 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 {
}
like image 782
Armeen Harwood Avatar asked Nov 21 '17 06:11

Armeen Harwood


1 Answers

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.

like image 58
Jadams Avatar answered Nov 27 '22 16:11

Jadams