Search code examples
angularngrxngrx-effects

problem with getting json data inside ngrx effect


I spent the last 2 days with a problem while trying to get a local json data (posts) to show it in the view (PostsComponent). in the console i get this error:

ERROR Error: Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.

but i think its not an object, i'm trying to return it as an array.

and another thing, when i try to log the result it is shown like this: { posts: [] } so it is an object contains an empty array.

i'm inside a module called BaseModule.

here is the shape of the post and the BaseModule state:

export interface BaseState {
  posts: Post[];
}

export class Post {
  id: number;
  title: string;
  content: string;
  imageSrc: string;
  author: {name: string, account: string, imageSrc: string};
  date: string;
  categories: string[];
}

I created posts.actions.ts file:

import { createAction, props } from "@ngrx/store";
import { Post } from "src/app/types";

export const getAllPosts = createAction('[PostsComponent] Load Posts');
export const getAllPostsSuccessed = createAction(
  '[[PostsComponent] Load Posts Successed',
  props<{posts: Post[]}>()
);
export const getAllPostsError = createAction(
  '[PostsComponent] Load Posts Error',
  props<{error: any}>()
);

then the posts.reducer.ts file:

import { createReducer, on } from "@ngrx/store";
import * as postsActions from "./posts.actions";
import { BaseState } from "src/app/types";

const initialState: BaseState = {
  posts: []
}

const _postsReducer = createReducer(
  initialState,
  on(postsActions.getAllPosts, state => state),
  on(postsActions.getAllPostsSuccessed, (state, { posts }) => {
    console.log(posts); return {...state, posts: posts};
  }),
  on(postsActions.getAllPostsError, (state, { error }) => { console.log(error); return state})
);

export function postsReducer(state: any, action: any){
  return _postsReducer(state, action);
}

and the posts.selectors.ts file:

import { createFeatureSelector, createSelector } from "@ngrx/store";
import { BaseState } from "src/app/types";

const selectBase = createFeatureSelector<BaseState>('base');

export const posts = createSelector(
  selectBase,
  (state: BaseState) => state.posts
);

then finally the posts.effects.ts file:

import { Actions, createEffect, ofType } from "@ngrx/effects";
import { HttpClient } from "@angular/common/http";
import { catchError, map, mergeMap } from "rxjs/operators";
import { Post } from "src/app/types";
import { Injectable } from "@angular/core";
import { of } from "rxjs";

@Injectable()
export class PostsEffects {

  getPosts = createEffect(() => this.actions.pipe(
    ofType('[PostsComponent] Load Posts'),
    mergeMap(() => this.http.get<{posts: Post[]}>('assets/posts.json').pipe(
      map(posts => ({type: '[PostsComponent] Load Posts Successed', posts: posts})),
      catchError(error => of({type: '[PostsComponent] Load Posts Error', error: error}))
    ))
  ));

  constructor(
    private http: HttpClient,
    private actions: Actions
  ) { }

}

and this is the posts.component.ts file:

import { Component, OnInit } from "@angular/core";
import { Store } from "@ngrx/store";
import { BaseState, Post } from "src/app/types";
import { getAllPosts } from "./state/posts.actions";
import { posts } from "./state/posts.selector";

@Component({
  selector: 'app-posts',
  templateUrl: './posts.component.html',
  styleUrls: ['./posts.component.scss']
})
export class PostsComponent implements OnInit {

  posts: Post[];
  limit = 6;

  constructor(private store: Store<BaseState>) { }

  ngOnInit(): void {
    this.store.dispatch(getAllPosts());
    this.store.select(posts).subscribe(posts => this.posts = posts);
    console.log(this.posts);
  }

  get getPosts(){
    return this.posts;
  }

  showMore(){
    this.limit = this.limit + 6;
  }

}

and the posts.component.html file:

<div class="post" *ngFor="let post of getPosts">
  <div class="title"><a [routerLink]="'/post/' + post.id.toString()">{{ post.title }}</a></div>
  <div class="content">
    {{ post.content | slice:0:30 }}<a [routerLink]="'/post/' + post.id.toString()">...Read more</a>
  </div>
  <div class="image">
    <img [src]="post.imageSrc">
  </div>
  <div class="author" routerLink="#">
    <img [src]="post.author.imageSrc">
    {{ post.author.name }}
  </div>
  <div class="date">{{ post.date }}</div>
  <div class="categories">
    <div class="category" *ngFor="let category of post.categories">
      {{ category }}
    </div>
  </div>
</div>
<div class="show-more" (click)="showMore()">Show More</div>

i'm new to angular and ngrx so please make the answers clear for a beginner.


Solution

  • Here were the problem was:

    first:

    there was a character "[" added by mistake in the actions file: '[[PostsComponent] Load Posts Successed'

    second:

    the json data was made in a wrong type like: {posts: [...]}