在 ngrx 效果中获取 json 数据的问题
problem with getting json data inside ngrx effect
过去 2 天我在尝试获取本地 json 数据 (posts) 以在视图 (PostsComponent) 中显示它时遇到了问题。
在控制台中我得到这个错误:
ERROR Error: Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
但我认为它不是一个对象,我正在尝试 return 它作为一个数组。
还有一件事,当我尝试记录结果时,它显示如下:{ posts: [] }
所以它是一个包含空数组的对象。
我在一个名为 BaseModule 的模块中。
这里是 post 的形状和 BaseModule 状态:
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[];
}
我创建了 posts.actions.ts 文件:
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}>()
);
然后 posts.reducer.ts 文件:
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);
}
和 posts.selectors.ts 文件:
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
);
最后 posts.effects.ts 文件:
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
) { }
}
这是 posts.component.ts 文件:
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;
}
}
和 posts.component.html 文件:
<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>
我是 angular 和 ngrx 的新手,所以请让初学者清楚地回答。
除非你有充分的理由使用 ngrx,否则我建议放弃它并使用标准服务。与使用 ngrx 尝试做最基本的事情相比,您可能需要围绕事情做的少量实施要少得多,也容易得多。
例如您的问题有 7 段代码来进行简单的 post 检索。
这是事件的简化版本,使用了基本的内置功能。
组件:
export class SomeComponent implements OnInit, OnDestroy {
private subs: Subscription[];
private _posts: Post[];
constructor(private readonly service: PostService) {
this.subs = [];
}
public get posts(): Post[] { return this._posts; }
public ngOnInit(): void {
this.subs.push(this.service.posts$
.subscribe((x: Post[]) => this.onPostsResponse(x)));
this.service.getPosts();
}
public ngOnDestroy(): void {
this.subs.forEach((x: Subscription) => x.unsubscribe());
}
private onPostsResponse(posts: Post[]): void {
this._posts = posts;
}
}
服务:
@Injectable()
export class PostService {
private posts: Post[];
private postsObs: Observer<Post[]>;
private _posts$: Observable<Post[]>;
constructor(private readonly http: HttpClient) {
this._posts$ = new Observable<Post[]>((x: Observable<Post[]>) => {this.postsObs = x;}).pipe(share());
}
public get posts$(): Observable<Post[]> { return this._posts$; }
public getPosts(): void {
const sub = this.http.get('.../posts')
.subscribe((response: Post[]) => {
sub.unsubscribe();
this.posts = response;
this.emitPosts();
});
}
private emitPosts(): void {
if (this.postsObs) this.postsObs.next(this.posts);
}
}
仅此而已,减去了您希望它具有的任何其他功能。我将 post 响应直接保存到 this.posts
变量,但我实际上并没有使用它,只是每次都覆盖它。
我看到你有更多的展示:它很容易扩展,这样你就可以包装你的 getPosts 方法,以便它 return 从变量中尽可能多地获取。一旦请求的 'more' post 超过了您缓存的 post 的数量,您就可以发出实际的 API 请求并 add 到 post,而不是直接覆盖它们,return 新的集合。
如果您将 post 请求保持在用户可以执行的任何操作之前一两页,则用户也不会有任何加载时间。当您在后台获取下一页时,他们将立即收到 post 的下一页。
直到你有一个 API 到位,你可以 return 任何你想要的虚拟数据(甚至可以将它放在 setTimeout
中以模拟一两秒的加载时间):
public getPosts(): void {
this.posts = dummyData;
setTimeout(() => {this.emitPosts();}, 2000}; // 2s delay
//const sub = this.http.get('.../posts')
// .subscribe((response: Post[]) => {
// sub.unsubscribe();
// this.posts = response;
// this.emitPosts();
// });
}
private dummyData: Post[] = [
{...},
{...},
{...}
];
现在你有了一个可以让你的应用程序模块提供的服务(因此是一个应用程序范围的单例),它正在获取、存储和发出任何组件都可以订阅和接收数据的帖子(或本身)调用 getPosts - 就像存储模式和本质上 ngrx 所做的一样,但没有所有这些效果和缩减器以及可怕的复杂性。
问题是:
第一个:
动作文件中误加了一个字符“[”:
'[[PostsComponent] Load Posts Successed'
第二个:
json 数据的类型有误,例如:{posts: [...]}
过去 2 天我在尝试获取本地 json 数据 (posts) 以在视图 (PostsComponent) 中显示它时遇到了问题。 在控制台中我得到这个错误:
ERROR Error: Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
但我认为它不是一个对象,我正在尝试 return 它作为一个数组。
还有一件事,当我尝试记录结果时,它显示如下:{ posts: [] }
所以它是一个包含空数组的对象。
我在一个名为 BaseModule 的模块中。
这里是 post 的形状和 BaseModule 状态:
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[];
}
我创建了 posts.actions.ts 文件:
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}>()
);
然后 posts.reducer.ts 文件:
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);
}
和 posts.selectors.ts 文件:
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
);
最后 posts.effects.ts 文件:
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
) { }
}
这是 posts.component.ts 文件:
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;
}
}
和 posts.component.html 文件:
<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>
我是 angular 和 ngrx 的新手,所以请让初学者清楚地回答。
除非你有充分的理由使用 ngrx,否则我建议放弃它并使用标准服务。与使用 ngrx 尝试做最基本的事情相比,您可能需要围绕事情做的少量实施要少得多,也容易得多。
例如您的问题有 7 段代码来进行简单的 post 检索。 这是事件的简化版本,使用了基本的内置功能。
组件:
export class SomeComponent implements OnInit, OnDestroy {
private subs: Subscription[];
private _posts: Post[];
constructor(private readonly service: PostService) {
this.subs = [];
}
public get posts(): Post[] { return this._posts; }
public ngOnInit(): void {
this.subs.push(this.service.posts$
.subscribe((x: Post[]) => this.onPostsResponse(x)));
this.service.getPosts();
}
public ngOnDestroy(): void {
this.subs.forEach((x: Subscription) => x.unsubscribe());
}
private onPostsResponse(posts: Post[]): void {
this._posts = posts;
}
}
服务:
@Injectable()
export class PostService {
private posts: Post[];
private postsObs: Observer<Post[]>;
private _posts$: Observable<Post[]>;
constructor(private readonly http: HttpClient) {
this._posts$ = new Observable<Post[]>((x: Observable<Post[]>) => {this.postsObs = x;}).pipe(share());
}
public get posts$(): Observable<Post[]> { return this._posts$; }
public getPosts(): void {
const sub = this.http.get('.../posts')
.subscribe((response: Post[]) => {
sub.unsubscribe();
this.posts = response;
this.emitPosts();
});
}
private emitPosts(): void {
if (this.postsObs) this.postsObs.next(this.posts);
}
}
仅此而已,减去了您希望它具有的任何其他功能。我将 post 响应直接保存到 this.posts
变量,但我实际上并没有使用它,只是每次都覆盖它。
我看到你有更多的展示:它很容易扩展,这样你就可以包装你的 getPosts 方法,以便它 return 从变量中尽可能多地获取。一旦请求的 'more' post 超过了您缓存的 post 的数量,您就可以发出实际的 API 请求并 add 到 post,而不是直接覆盖它们,return 新的集合。
如果您将 post 请求保持在用户可以执行的任何操作之前一两页,则用户也不会有任何加载时间。当您在后台获取下一页时,他们将立即收到 post 的下一页。
直到你有一个 API 到位,你可以 return 任何你想要的虚拟数据(甚至可以将它放在 setTimeout
中以模拟一两秒的加载时间):
public getPosts(): void {
this.posts = dummyData;
setTimeout(() => {this.emitPosts();}, 2000}; // 2s delay
//const sub = this.http.get('.../posts')
// .subscribe((response: Post[]) => {
// sub.unsubscribe();
// this.posts = response;
// this.emitPosts();
// });
}
private dummyData: Post[] = [
{...},
{...},
{...}
];
现在你有了一个可以让你的应用程序模块提供的服务(因此是一个应用程序范围的单例),它正在获取、存储和发出任何组件都可以订阅和接收数据的帖子(或本身)调用 getPosts - 就像存储模式和本质上 ngrx 所做的一样,但没有所有这些效果和缩减器以及可怕的复杂性。
问题是:
第一个:
动作文件中误加了一个字符“[”:
'[[PostsComponent] Load Posts Successed'
第二个:
json 数据的类型有误,例如:{posts: [...]}