使用featureSelector ngrx时无法读取undefined地图的属性

时间:2017-11-21 06:15:52

标签: angular ngrx

REPO:https://github.com/morningharwood/platform/tree/feature/ngrx-firebase

当我尝试订阅我的postAll的post ngrx实体功能时,我收到错误:

  

无法读取未定义的地图属性

这必须是一个简单的拼写错误我正在逐字逐句地阅读教程。

奇怪的是,如果我使用字符串选择器:

 this.selected$ = this.store.select('post');

 this.selected$.subscribe(console.log); // no error here.

我没有错误。

问题: 使用ngrx实体时,如何让我的selectAll选择器注销ngrx商店集合post中的所有帖子?

我的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,
};

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
  }
}

我的forFeatureforRoot模块:

@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 {
}

4 个答案:

答案 0 :(得分:4)

对于将来遇到这种情况的人,可以通过稍微重构选择器来解决。

根据此处ngrx.io/entity/adapter(在发布时有些不完整)的文档,您需要使用createFeatureSelector函数选择featureSate,然后将该子状态与适配器选择器一起使用以选择切片那个状态。

更改此:

post.reducer.ts

export const getPostState = createFeatureSelector<PostState>('post');
export const {
  selectIds,
  selectEntities,
  selectAll: selectAllPosts,
  selectTotal,
} = postAdapter.getSelectors(getPostState);

对此:

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
);

这将选择postState功能的子状态。

希望这会有所帮助

编辑:因此,似乎上述两个示例是等效的,我所面临的问题与Matthew Harwood相同,并且使用ActionReducerMap的方法不正确。

我认为文档对此可能有些困惑,因为他们会认为和的状态与要素状态(例如延迟加载状态)的状态相同。

虽然实体状态具有ID和实体等属性,但是它们不需要像每个相似特征状态那样的化简,而仅需要一个化简器来覆盖该模型的所有实体状态。

例如

export const initialState: PostState = postAdapter.getInitialState(postInitialState);

PostAdapterState不需要一个简化器映射,因为一个简化器

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;
  }
}

涵盖所有实体状态更新。

如果您在功能状态(例如以下示例)中有多个适配器状态,则需要一个简化器映射。

让我们说您有一个“图书馆”功能状态,其中包含书籍和杂志的实体。看起来像下面的样子。

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

然后您的库功能状态可能类似于

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
};

然后将功能状态添加到LibraryModule导入数组中的商店中。

library.module.ts

...
StoreModule.forFeature<LibraryFeatureState>('libraryState', 
libraryReducersMap),
...

希望这对其他人有帮助。

答案 1 :(得分:1)

我尝试使用以下内容创建选择器时遇到了相同的问题

export const selectFuturMenus = createSelector(
  selectMenuState,
  fromMenu.selectAll,
  (state, menus: Menu[], props) => menus.filter(menu => {
    let startMmt = moment(menu.start_date, 'YYYY-MM-DD');
    let endMmt = moment(menu.end_date, 'YYYY-MM-DD');
    let mmt = moment();
    console.log(startMmt, endMmt);
    return mmt.isBefore(endMmt);
  })
);

似乎我们不能在过滤器功能之前使用适配器选择器。 根据NgRX文档,一个简单的解决方案是分解选择器,像这样

export const selectAllMenus = createSelector(
  selectMenuState,
  fromMenu.selectAll,
);

export const selectFuturMenus = createSelector(
  selectAllMenus,
  (menus: Menu[], props) => menus.filter(menu => {
    let startMmt = moment(menu.start_date, 'YYYY-MM-DD');
    let endMmt = moment(menu.end_date, 'YYYY-MM-DD');
    let mmt = moment();
    return mmt.isBefore(endMmt);
  })
);

答案 2 :(得分:0)

ActionMapReducer中创建了forFeature('post', postReducerMap);我还需要弄清楚如何一起使用实体和ActionMapReducer。

答案 3 :(得分:0)

几个小时以来,我一直在为同一个问题苦苦思索,最后我意识到问题的根源与reduce函数有关,具体地说,我的reducer函数并没有在还原结束时返回默认状态切换语句。

正如我所说,花了一段时间才找到答案,希望我的回答能帮助其他人比我更快地解决此问题。

switch (action.type) {

    case ActionType:
      return adapter.addAll(action.payload, state);

    default:
      return state;
  }