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:
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 {}
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);
}
...
}
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'} }))
);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With