Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 {}
like image 719
antonyboom Avatar asked Dec 08 '17 16:12

antonyboom


3 Answers

This isn't an answer to antonyboom's specific issue, but rather a helpful hint for others who arrived here based on the title of the question: it appears that NGRX's Store.select will return the store observable as a default in case it can't find data at the path you've queried. think Apache 404 error.

I had to revise my type casting to ensure typescript wasn't enforcing an incorrect store structure. specifically, note that the interface specify for the first argument into your reducer is not the same as that which you inject into your components.

Model, Reducer:

export interface AppState {
  textinput: string;
  integerVariable: number;
}
export interface State {
  app: AppState;
}

export function reducerFunction (
  state: AppState,
  action: Actions
) {
  switch (action.type) {
    ...
  }
}

app.module.ts:

StoreModule.forRoot({
  app: reducerFunction
})

app.component.ts

export class AppComponent {
constructor(private store: Store<State>) {
    this.currentSlide = store.select(s => s.app.textinput);
}
...
}
like image 188
ryanrain Avatar answered Oct 17 '22 07:10

ryanrain


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'} }))
        );
    
like image 2
Aravind Avatar answered Oct 17 '22 06:10

Aravind


The name provided in reducer import part MUST be same as the parameter name of the State

Store:

export interface AppState {
  logs: string[];
}

export interface AppStore{
  appState: AppState; // (1)
}

app.module.ts import:

StoreModule.forRoot({
  appState: reducerFunction // (2)
})

So, (1) and (2) (appState) MUST be same.

like image 1
Amin Saqi Avatar answered Oct 17 '22 05:10

Amin Saqi