Platform: [Support] Cannot read property 'entities' of undefined

Created on 24 Mar 2018  路  2Comments  路  Source: ngrx/platform

I get this error when trying pipe to State via selector. I just wanted to adapt this EntityState solution for hanlding entities based on Example-app Books. I already got some ngrx modules working in my app, this is my first approach to this EntityState solution.

I spent hours on this, please help.

image

reducers/index.ts

import` * as fromServiceProducts from './service-product';
import * as fromCollection from './collection';
import * as fromRoot from '../../reducers';
import { createFeatureSelector, createSelector } from '@ngrx/store';

export interface ServiceProductsState {
    collection: fromCollection.State,
    serviceProducts: fromServiceProducts.State
}

export interface State extends fromRoot.State {
    serviceProducts: ServiceProductsState;
}

export const reducers = {
    serviceProduct: fromServiceProducts.reducer,
    collection: fromCollection.reducer
};

//Base ServiceProduct state setup
export const getServiceProductsState = createFeatureSelector<ServiceProductsState>('serviceProducts');

export const getServiceProductEntitiesState = createSelector(
    getServiceProductsState,
    state => state.serviceProducts
);

export const getSelectedServiceProductId = createSelector(
    getServiceProductEntitiesState,
    fromServiceProducts.getSelectedId
);

export const {
    selectIds: getServiceProductIds,
    selectEntities: getServiceProductEntities,
    selectAll: getAllServiceProducts,
    selectTotal: getTotalServiceProducts,
} = fromServiceProducts.adapter.getSelectors(getServiceProductEntitiesState);

// COLLECTION
export const getCollectionState = createSelector(
    getServiceProductsState,
    (state: ServiceProductsState) => state.collection
);

export const getCollectionServiceProductIds = createSelector(
    getCollectionState,
    fromCollection.getIds
);

export const getServiceProductCollection = createSelector(
    getServiceProductEntities,
    getCollectionServiceProductIds,
    (entities, ids) => {
        console.log(entities, ids)
        return ids.map(id => entities[id]);
    }
);

reducers/collection.ts

import {
  CollectionActionTypes,
  CollectionActions,
} from './../actions/collection';

export interface State {
  loaded: boolean;
  loading: boolean;
  ids: string[];
}

const initialState: State = {
  loaded: false,
  loading: false,
  ids: [],
};

export function reducer(
  state = initialState,
  action: CollectionActions
): State {
  switch (action.type) {
    case CollectionActionTypes.Load: {
      return {
        ...state,
        loading: true
      };
    }

    case CollectionActionTypes.LoadSuccess: {
      return {
        loaded: true,
        loading: false,
        ids: action.payload.map(serviceProduct => serviceProduct.ServiceProductId)
      };
    }

    case CollectionActionTypes.LoadFail: {
      return {
        loaded: true,
        loading: false,
        ids: []
      };
    }

    default: {
      return {
        ...state
      }
    }
  }
}

export const getLoaded = (state: State) => state.loaded;

export const getLoading = (state: State) => state.loading;

export const getIds = (state: State) => state.ids;

reducers/service-products.ts

import { createSelector } from '@ngrx/store';
import { createEntityAdapter, EntityAdapter, EntityState } from '@ngrx/entity';
import { ServiceProduct } from '../../shared/publicModels/ServiceProduct';
import { ServiceProductEntityStateActionTypes, ServiceProductsEntityStateActions } from '../actions/service-products';

export const adapter: EntityAdapter<ServiceProduct> = createEntityAdapter<ServiceProduct>({
    selectId: (serviceProduct: ServiceProduct) => serviceProduct.ServiceProductId,
    sortComparer: false,
});

export interface State extends EntityState<ServiceProduct> {
    selectedServiceProductId: string | null
}

export const initialState: State = adapter.getInitialState({
    selectedServiceProductId: null
});

export function reducer(
    state = initialState,
    action: ServiceProductsEntityStateActions
): State {
    switch (action.type) {
        case ServiceProductEntityStateActionTypes.SELECT: {
            return {
                ...state,
                selectedServiceProductId: action.payload,
            };
        }

        default: {
            return {
                ...state
            };
        }
    }
}

export const getSelectedId = (state: State) => state.selectedServiceProductId;

actions/collection.ts

export enum CollectionActionTypes {
  Load = '[Collection] Load',
  LoadSuccess = '[Collection] Load Success',
  LoadFail = '[Collection] Load Fail',
}

/**
 * Load Collection Actions
 */
export class Load implements Action {
  readonly type = CollectionActionTypes.Load;

  constructor(public payload: any) {}
}

export class LoadSuccess implements Action {
  readonly type = CollectionActionTypes.LoadSuccess;

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

export class LoadFail implements Action {
  readonly type = CollectionActionTypes.LoadFail;

  constructor(public payload: any) {}
}

export type CollectionActions =
  | Load
  | LoadSuccess
  | LoadFail;

service-product.component.ts
Place where I use getServiceProductCollection selector.

export class ServiceProductsComponent implements OnInit {

    serviceProducts$: Observable<ServiceProduct[]>;

    constructor(private store: Store<fromServiceProducts.State>) {
        this.serviceProducts$ = store.pipe(select(fromServiceProducts.getServiceProductCollection));
    }

    ngOnInit() {
        this.store.dispatch(new collection.Load(new GetServiceProductsQuery([], true)));
    }
}

component.html

<div *ngFor="let serviceProduct of serviceProducts$  |  async">
    <h1>{{serviceProduct.Name}}</h1>
</div>

reducers/index.ts

import {
    ActionReducerMap,
    createSelector,
    createFeatureSelector,
    ActionReducer,
    MetaReducer,
  } from '@ngrx/store';
  import { environment } from '../../environments/environment';
  import { RouterStateUrl } from '../shared/utils';
  import * as fromRouter from '@ngrx/router-store';

  import { storeFreeze } from 'ngrx-store-freeze';  

  export interface State {
    router: fromRouter.RouterReducerState<RouterStateUrl>;
  }

  export const reducers: ActionReducerMap<State> = {
    router: fromRouter.routerReducer,
  };

  export function logger(reducer: ActionReducer<State>): ActionReducer<State> {
    return function(state: State, action: any): State {
      console.log('state', state);
      console.log('action', action);

      return reducer(state, action);
    };
  }

  export const metaReducers: MetaReducer<State>[] = !environment.production
    ? [logger, storeFreeze]
    : [];

app.module.ts (chunk with Store initialize)

imports: [
    ...
    StoreModule.forRoot(reducers, { metaReducers }),
    StoreRouterConnectingModule.forRoot({
      stateKey: 'router',
    }),
    EffectsModule.forRoot([RouterEffects]),
    StoreDevtoolsModule.instrument({
      name: 'NgRx Mammon Store DevTools',
      logOnly: environment.production,
    }),
    ...
]

service-product.module.ts

@NgModule({
    imports: [
        CommonModule,
        EffectsModule.forFeature([ServiceProductsEffects]),
        StoreModule.forFeature('serviceProducts', reducers),
        TranslateModule,
        CostsModule
    ],
    declarations: [ServiceProductsComponent, ServiceProductDetailsComponent],
    providers: [ServiceProductsService]
})

export class ServiceProductsModule { }

Most helpful comment

How are you importing the reducers for this line?

        StoreModule.forFeature('serviceProducts', reducers),

If you're importing it as import * as reducers from './reducers';, that explains why its undefined. If that's the case, your import should be import * as fromSearchProducers from './reducers'; and fromSearchProducts.reducers. If you need further support, use the gitter channel

All 2 comments

image

image

Maybe there is something wrong with my default collection state?

How are you importing the reducers for this line?

        StoreModule.forFeature('serviceProducts', reducers),

If you're importing it as import * as reducers from './reducers';, that explains why its undefined. If that's the case, your import should be import * as fromSearchProducers from './reducers'; and fromSearchProducts.reducers. If you need further support, use the gitter channel

Was this page helpful?
0 / 5 - 0 ratings