Search code examples
angularngrxngrx-store

NgRX store select returns store state instead of data


I'm trying to implement ngrx v4 to my angular project and struggling with getting data from ngrx store. I wrote Actions, Reducer and Effects based on provided api documentation on github page and it seems to me everything is correct but instead of data I get store State like this:

enter image description here

So I appreciate if somebody could help me to figure out where my mistake is. Thanks in advance. My code below

interface.ts

export interface Category {
  id?: number;
  name: string;
  used: boolean;
}

actions.ts

import { Action } from '@ngrx/store';
import {Category} from '../categories';

export const GET_CATEGORIES  = '[Category] Get data';
export const GET_CATEGORIES_ERROR = '[Category] Get data error';
export const GET_CATEGORIES_SUCCESS  = '[Category] Get data success ';

export class GetCategories implements Action {
  readonly type = GET_CATEGORIES;
}
export class GetCategoriesError implements Action {
  readonly type = GET_CATEGORIES_ERROR;
}
export class GetCategoriesSuccess implements Action {
  readonly type = GET_CATEGORIES_SUCCESS;
  constructor(public payload: Category[]) { }
}

export type All = GetCategories | GetCategoriesSuccess | GetCategoriesError

reducer.ts

import * as CategoriesActions from './categories.actions';
import {Category} from '../categories';

export type Action = CategoriesActions.All;

export function categoriesReducer (state: Category[], action: Action) {

  switch (action.type) {

    case CategoriesActions.GET_CATEGORIES: {
      return state;
    }

    case CategoriesActions.GET_CATEGORIES_ERROR: {
      return state;
    }

    case CategoriesActions.GET_CATEGORIES_SUCCESS: {
      return action.payload;
    }

    default: {
      return state;
    }
  }
}

effects.ts

...
import {CategoriesService} from './categories.service';

@Injectable()
export class CategoriesEffects {

  @Effect() getCategories$: Observable<Action> = this.actions$
    .ofType('GET_CATEGORIES')
    .mergeMap(action => this.categoriesService.getCategories()
      .map(data => ({type: 'GET_CATEGORIES_SUCCESS', payload: data }))
      .catch(() => of({ type: 'GET_CATEGORIES_ERROR', payload: {message: 'Oops something is wrong'} }))
    );

  constructor(
    private actions$: Actions,
    private categoriesService: CategoriesService
  ) {}
}

component.ts

interface CategoryState {
  categories: Category[]
}

@Component({
  selector: 'app-categories',
  templateUrl: './categories.component.html',
  styleUrls: ['./categories.component.css'],
})

export class CategoriesComponent implements OnInit {

  categories$: Observable<Category[]>;

  constructor(private store: Store<CategoryState>) {

    console.log(this.categories$) // undefined

    this.categories$ = this.store.select('categories');

    console.log(this.categories$) // store state
  }

  ngOnInit() {
    this.getCategories()
  }

  getCategories () {
    this.store.dispatch({ type: 'GET_CATEGORIES' });
  } 
}

module

@NgModule({
  imports: [
    ...
    StoreModule.forRoot({ categories: categoriesReducer }),
    EffectsModule.forRoot([CategoriesEffects]),
  ],
  declarations: [ CategoriesComponent ],
  providers:    [ CategoriesService ],
  entryComponents: []
})
export class CategoriesModule {}

Solution

  • Since you are using ngrx/store : 4.1

    • You should have a reducer factory to inject the reducers to the StoreModule

      import {ActionReducerMap} from '@ngrx/store';
      import {Category} from '../categories';
      import * as categoryReducer from './categories.reducer';
      
      export interface CategoryState {
        categories: Category[]
      }
      
      export interface AppStates {
        categoryState: CategoryState;
      }
      
      export const categoryReducers: ActionReducerMap<AppStates> = {
        categoryState: categoryReducer.reducer
      };
      
    • Use the reducer factory to inject into the module as below,

      StoreModule.forRoot(categoryReducers)
      import {categoryReducers} from './store/categories.reducer.factory';
      
    • Your constructor should take the AppStates as the type for Store as

      constructor(private store: Store<AppStates>){}
      
    • And your effect should use be

      @Effect() 
      getCategories$: Observable<Action> = this.actions$
          .ofType(CategoriesActions.GET_CATEGORIES)
          .mergeMap(action => this.categoriesService.getCategories()
                .map(data => ({type: CategoriesActions.GET_CATEGORIES_SUCCESS, payload: data }))
                .catch(() => of({ type: CategoriesActions.GET_CATEGORIES_ERROR, payload: {message: 'Oops something is wrong'} }))
          );