0
votes

I'm trying to implement a NgRx store using @ngrx/entity lib, but I can't retrieve any data by using @ngrx/entity getSelectors. Redux Devtools shows my collection loaded by Effect()'s as entities properly. So my Effect is working properly. Now I want to select the data as array by using the selectAll selector from adapter.getSelectors() function. My reducer index looks as follows

reducers/index.ts

import { ActionReducerMap, createFeatureSelector, createSelector } from '@ngrx/store';

import * as fromModels from './models.reducer';
import { EntityState } from '@ngrx/entity';

export interface State {
  models: fromModels.State;
}

export const reducers: ActionReducerMap<State> = {
  models: fromModels.reducer
};

export const getModelsState = createFeatureSelector<fromModels.State>('models');
export const { selectAll: getAllModels } = fromModels.adapter.getSelectors(getModelsState);

reducers/models.reducer.ts

import { createFeatureSelector, createSelector } from '@ngrx/store';
import { createEntityAdapter, EntityState, EntityAdapter } from '@ngrx/entity';

import { ModelsActions, ModelsActionTypes } from '../actions';
import { Model } from '../../models/model';

export const adapter: EntityAdapter<Model> = createEntityAdapter<Model>({
  selectId: model => model.id,
  sortComparer: (modelA, modelB) => modelA.id === modelB.id ? 0 : (modelA.id === modelB.id ? 1 : -1)
});

export interface State extends EntityState<Model> {}
const initialState = adapter.getInitialState();

export function reducer(state = initialState, action: ModelsActions): State {
  switch (action.type) {

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

    default: return state;
  }
}

In my container component I want to select the data by the ngrx/entity selector and display the data using the async pipe. (I reduced the template)

models.component.ts

// All other import statements
import { Store, select } from '@ngrx/store';
import * as fromFeature from '../store';

@Component({
  template: `{{models$ | async}} | json`
})
export class ModelsComponent implements OnInit {
  models$: Observable<Model[]>;

  constructor(private store: Store<fromFeature.State>) {}

  ngOnInit() {
    this.models$ = this.store.select(fromFeature.getAllModels);
    this.store.dispatch(new fromFeature.LoadModels());
  }
}

The console prints an error

ModelsComponent.html:2
ERROR TypeError: Cannot read property 'ids' of undefined
at selectIds (entity.es5.js:45)
at eval (store.es5.js:572)

Any suggestions or ideas solving this? Thanks!

UPDATE [Requested template]

The template just subscribes to the models$ observable, same as in the reduces template. The model-card.component.ts just gets Input()'s and ng-content.

<app-model-card
  *ngFor="let model of models$ | async"
  [logo]="model.id"
  [routerLink]="[model.id]">
  {{model.title}}
</app-model-card>

UPDATE [actions requested]

actions/models.action.ts

import { Action } from '@ngrx/store';
import { Model } from '../../models/model';

export enum ModelsActionTypes {
  Load = '[Models] Load',
  LoadSuccess = '[Models] Load Success',
  LoadFailed = '[Models] Load Failed'
}

export class LoadModels implements Action {
  readonly type = ModelsActionTypes.Load;
}

export class LoadModelsSuccess implements Action {
  readonly type = ModelsActionTypes.LoadSuccess;

  constructor(public payload: Model[]) {}
}

export class LoadModelsFailed implements Action {
  readonly type = ModelsActionTypes.LoadFailed;

  constructor(public payload: any) {}
}

export type ModelsActions =
  LoadModels |
  LoadModelsSuccess |
  LoadModelsFailed;

models.effects.ts

import { Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { map, switchMap, catchError } from 'rxjs/operators';

import { ModelsActionTypes, LoadModelsSuccess, LoadModelsFailed } from 
  '../actions';
import { ModelsService } from '../../services/models.service';

@Injectable()
export class ModelsEffects {
  constructor(
    private actions$: Actions,
    private modelsService: ModelsService
  ) {}

  @Effect()
  loadModels$ = this.actions$
    .ofType(ModelsActionTypes.Load)
    .pipe(
      switchMap(() => {
        return this.modelsService.getModels().pipe(
          map(models => new LoadModelsSuccess(models)),
          catchError(error => of(new LoadModelsFailed(error)))
        );
      })
    );
}
1
Please provide the content of ModelsComponent.html.Lynx 242
@DiabolicWords Updated my question appended template. Can't imagine the error is in the template. The reduced template causes the same error.Felix Lemke
Maybe this.models$ = this.store.select(fromFeature.getAllModels) -> this.models$ = this.store.select(fromFeature.selectAll)?Richard Matsen
@RichardMatsen Doesn't work, same error.Felix Lemke
I notice './models.reducer' but reducers/model.reducers.ts - but that's got to be a typo in the question, right?Richard Matsen

1 Answers

3
votes

Oh damn, I figured it out. The problem was the createFeatureSelector, which just selects features (as its name is saying). I registered the module as StoreModule.forFeature('configuration', reducers)

The solution then is getting the ConfigurationState from Feature selector and the models state from default selector.

reducers/index.ts

[...]
export const getConfigurationState = createFeatureSelector<ConfigurationState>('configuration');
export const getModelsState = createSelector(
  getConfigurationState,
  (configuration) => configuration.models
);

export const {
  selectIds,
  selectEntities,
  selectAll
} = fromModels.adapter.getSelectors(getModelsState);

Thanks for all your help!