Search code examples
angularselectorstatestorengrx

ngrx 4 selectors returning entire state instead of sub states


For some reason all of my selectors are returning the entire state object instead of the sub states that I thought they should. For example, I am trying to just return the customer State from reducers/customer.ts but instead I get an object with the entire State from index.ts.

I choose the particular structure below after examining the ngrx example app from github, clearly I am doing something(s) wrong.

File structure: enter image description here

Here is actions/customer.ts:

import { Action } from '@ngrx/store';
import { State } from '../reducers/customer';

export enum CustomerActionTypes {
  Add = '[Customer] Add Selected Customer to Store'
}

export class Add implements Action {
  readonly type = CustomerActionTypes.Add;
  constructor(public payload: State) {
    console.log("from action: " + JSON.stringify(payload,null,2));
  }
}

export type CustomerActions = Add

And reducers/customer.ts:

import { ActionReducer, Action } from '@ngrx/store';
import { CustomerActionTypes, CustomerActions } from '../actions/customer';

export interface State {
  name: string;
  status: string;
  phone: string;
  stage: string;
  type: string;
  id: string;
  street: string;
  city: string;
  postalCode: string;
  email: string;
  state: string;
}

export function reducer(
  state: State,
  action: CustomerActions
): State {
  switch (action.type) {
    case CustomerActionTypes.Add:
      return action.payload

    default:
      return state;
  }
}

export const getCustomerState = (state: State) => state;

And reducers/index.ts:

//For reducers map
import * as fromMainNav from './main-nav-ui';
import * as fromTradeUI from './trade-ui';
import * as fromAppts from './appointments';
import * as fromCustomer from './customer';


//Whole application state
export interface State {
  mainNavUI: fromMainNav.State;
  tradeUI: fromTradeUI.State;
  appointments: fromAppts.State;
  selectedCustomer: fromCustomer.State;
}

//Reducer map
export const reducers = {
  mainNavUI: fromMainNav.reducer,
  tradeUI: fromTradeUI.reducer,
  appointments: fromAppts.reducer,
  selectedCustomer: fromCustomer.reducer
};

And imports in app.module.ts:

imports: [
    ...
    StoreModule.forRoot(reducers)
  ],

And lastly how I am am wiring it up in a component and using selector:

...
import * as fromCustomer from '../../../shared/state/reducers/customer';
...

public cust$: Observable<fromCustomer.State>;

constructor(
    ...
    public ngrxStore: Store<fromCustomer.State>
  ) {
      this.cust$ = ngrxStore.select(fromCustomer.getCustomerState);
    }

Any help would be appreciated, thanks!


Solution

  • Try using createFeatureSelector inside reducers/index.ts to start from a top level feature state:

    export const getAppState = createFeatureSelector<State>('wholeApp');
    

    Then use it in a different selector to access your customer reducer:

    export const getCustomer = createSelector(
      getAppState,
      (state: State) => state.selectedCustomer
    );
    

    From there you can keep going deeper into your state by chaining selectors, as example, if your component just needed to know about a customer's email adresse, it could just subscribe to this:

    export const getEmail = createSelector(
       getCustomer,
       (state: fromCustomer.State) => state.email
    );
    

    For more details (and examples) I recommend checking this great article by Todd Motto:

    https://ultimatecourses.com/blog/ngrx-store-understanding-state-selectors