Search code examples
angularngrx

I am not able to fetch items from the NgRx store and the observable remains undefined


import { Post } from './models/post.model';
import { AppActions, SET_POSTS } from './app.actions';

export interface State {
    posts: Post[]
}

const initialState: State = {
    posts: []
}

export function appReducer(state = initialState, action: AppActions) {
    switch(action.type) {
        case SET_POSTS:
            return {
                ...state,
                posts: action.payload
            }
        default: 
            return state;
    }
}

export const getPosts = (state: State) => state.posts;

So this is what I have on the reducer end, I don't think there's anything wrong with it I wrote it from the examples provided on the official documentation.

  ngOnInit() {
    this.posts$ = this.store.select(fromPost.getPosts);
    this.postService.getPosts();
  }

Now, the postService is working and I have been able to confirm this by logging it, but for some reason it never gets populated.

 .subscribe((posts: Post[]) => {
    this.store.dispatch(new AppAction.SetPosts(posts));
 }

When I log posts, I get the expected output, so I am wondering if it's the dispatch that's being improperly called or handled.

import { Action } from '@ngrx/store';
import { Posts } from './models/post.model';

export const SET_POSTS = 'Set Posts';

export class SetPosts implements Action {
    readonly type = SET_POSTS;
  
    constructor(public payload: Post[]) {}
}

export type AppActions = SetPosts;

This is the post actions, so I don't see anything wrong with it or is there?

Update:

   this.posts$ = this.store.select(fromPost.selectPosts);
   this.postService.getPosts();
   console.log(this.posts$);
   this.posts$.subscribe( value => console.log(value));
   //Object { _isScalar: false, actionsObserver: {…}, reducerManager: {…}, source: {…}, operator: {…} }

   //undefined

I also made some changes in the reducer:

const getPosts = (state: State) => state.posts;

export const selectState = (state: State) => state;
export const selectPosts = createSelector(
  selectState,
  getPosts
);

I am still getting nothing when I do this in my html file:

<div *ngIf="(posts$ | async) as posts">
  <div *ngFor="let post of posts">
    {{ post.title}}
  </div>
</div>

Solution

  • Short answer, you have write a selector to bind the post to the UI.

    Create Selector.ts file

    import { createSelector } from '@ngrx/store';
    import * as fromPost from 'post.reducer'
    
    export const selectState = (state: State) => state;
    export const selectPost = createSelector(
      selectState,
      fromPost.getPosts
    );
    

    In your component

    constructor() {
      this.store.dispatch(new LoadPost())
    }
    
    ngOnInit() {
        this.posts$ = this.store.select(selectPost);
      }
    

    Create an effect that calls postService, async services are side effects to the store and should be handled through Effects. Documentation for Setting up effects

    create posts action.ts

    import { Action } from '@ngrx/store'
    
    export enum PostActionTypes {
      LoadPost = '[Post] Load Post',
      SetPosts = '[Post] Load Post Success',
      LoadPostFailure = '[Post] Load Post Failure',
    }
    
    export class LoadPost implements Action {
      readonly type = PostActionTypes.LoadPost
    }
    
    export class SetPosts implements Action {
      readonly type = PostActionTypes.SetPosts
      constructor(public payload: Post[]) {}
    }
    
    export class LoadPostFailure implements Action {
      readonly type = PostActionTypes.LoadPostFailure
      constructor(public payload: any) {}
    }
    
    export type PostActions = 
    | LoadPost
    | SetPosts
    | LoadPostFailure
    

    create Post effect.ts

    import { Injectable } from '@angular/core'
    import { Actions, ofType } from '@ngrx/effects'
    import { of } from 'rxjs'
    import { catchError, map, switchMap } from 'rxjs/operators'
    
    @Injectable()
    export class PostEffects {
      constructor(private postService: PostService, private actions$: Actions) {}
    
      @Effect()
      loadPosts$ = this.actions$.pipe(
        ofType(PostActions.LoadPost),
        switchMap(() => {
          return this.postService.getPosts().pipe(
            map(posts => new SetPosts(posts)),
            catchError(error => of(new LoadPostFailure(error)))
          )
        })
      )
    }
    

    NOTE: Effects are Injectable classes, make sure you add them to

    NgModule({
      declarations: [],
      imports: [
        ...    
        EffectsModule.forRoot([ ... effects ]), // Effect classes as CSV
        ...
      ]
    })