模板中未显示 ngrx 状态
ngrx state is not shown in the template
我正在使用 angular & ngrx 开发博客。
post从服务器接收,显示在posts-list.component
.
当用户点击 post 标题时,他应该被导航到路线 /posts/:id
以查看完整的 post,并且 post 应该保存在商店和你的 localStorage 为 selectedPost
.
问题是 selectedPost
已在商店和本地存储中设置,但未显示在视图中。
代码如下:(未应用任何样式)
模块:
app.module.ts:
// imports...
@NgModule({
declarations: [
AppComponent,
BaseComponent,
HomeComponent,
NavigationComponent,
FooterComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
HttpClientModule,
StoreModule.forRoot({}),
StoreDevtoolsModule.instrument(),
EffectsModule.forRoot(),
FontAwesomeModule,
PostsModule
],
providers: [AppPreloadingStrategyService],
bootstrap: [AppComponent]
})
export class AppModule { }
posts.module.ts:
// imports..
@NgModule({
declarations: [
PostsListComponent,
PostComponent
],
imports: [
CommonModule,
RouterModule,
ReactiveFormsModule,
StoreModule.forFeature(featureKey,postReducer),
EffectsModule.forFeature([PostEffects])
],
exports: [
PostsListComponent,
PostComponent
]
})
export class PostsModule { }
状态:
post.actions.ts:
// imports...
export const loadPosts = createAction('[Post] Load');
export const loadPostsSuccess = createAction('[Post] Load Success', props<{posts: Post[]}>());
export const loadPostsError = createAction('[Post] Load Error', props<{error: string}>());
export const setSelectedPost = createAction('[Post] Set Selected Post', props<{post: any}>());
export const selectPost = createAction('[Post] Select', props<{post: Post}>());
export const updatePost = createAction('[Post] Update', props<{update: Update<Post>}>());
export const selectUpdatedPost = createAction('[Post] Select Updated', props<{title: string, content: string}>());
post.reducer.ts:
export interface PostsState extends EntityState<Post> {
selectedPost: Post;
error: any;
}
export function selectItemId(a: Post): string {
return a.id.toString();
}
export function sortByName(a: Post, b: Post): number {
return a.title.localeCompare(b.title);
}
export const postAdapter: EntityAdapter<Post> = createEntityAdapter<Post>();
export const initialState = postAdapter.getInitialState({selectId: selectItemId,
sortComparer: sortByName,selectedPost: undefined, error: ''});
export const _postReducer = createReducer(
initialState,
on(postActions.loadPostsSuccess, (state , { posts }) => {
return postAdapter.setAll(posts, state);
}),
on(postActions.loadPostsError, (state, {error}) => {
return {...state, error: error};
}),
on(postActions.updatePost, (state, {update}) => {
return postAdapter.updateOne(update, state);
}),
on(postActions.setSelectedPost, (state, {post}) => {
return {...state, selectedPost: post};
})
);
export function postReducer(state: any, action: Action) {
return _postReducer(state, action);
}
// selectors
export const featureKey = "posts";
const selectPostsFeature = createFeatureSelector<PostsState>(featureKey);
const {selectAll} = postAdapter.getSelectors();
export const getPosts = createSelector(
selectPostsFeature,
selectAll
);
export const getSelectedPost = createSelector(
selectPostsFeature,
(state: PostsState) => state.selectedPost
);
post.effects.ts:
@Injectable()
export class PostEffects {
constructor(private actions$: Actions, private http: HttpClient) {}
loadPosts$ = createEffect(() => this.actions$.pipe(
ofType('[Post] Load'),
mergeMap(() => this.http.get('http://localhost:3000/posts').pipe(
map(posts => ({type: '[Post] Load Success', posts: posts})),
catchError(err => of({type: '[Post] Load Error', error: err}))
))
));
}
组件:
post.component.ts:
@Component({
selector: 'app-post',
templateUrl: './post.component.html',
styleUrls: ['./post.component.scss']
})
export class PostComponent implements OnInit {
constructor(
private store: Store,
private fb: FormBuilder,
private route: ActivatedRoute,) { }
post$: Observable<Post>;
postUrl: any;
formGroup = this.fb.group({
title: [''],
content: ['']
});
ngOnInit(): void {
if(localStorage.getItem('theSelectedPost') !== null) {
this.store.dispatch(setSelectedPost({post: localStorage.getItem('theSelectedPost')}));
}
this.post$ = this.store.pipe(select(getSelectedPost));
this.postUrl = this.route.snapshot.paramMap.get("id");
}
updatePost(post: Post){
this.store.dispatch(updatePost({update: {id: this.postUrl, changes: post}}));
}
}
post.component.html:
<div class="title">
{{(post$ | async)?.title}}
</div>
<div class="content">
{{(post$ | async)?.content}}
</div>
<img [src]="(post$ | async)?.imageSrc">
<div class="categories">
<div class="category" *ngFor="let category of (post$ | async)?.categories">
{{category}}
</div>
</div>
<form [formGroup]="formGroup">
Update Post: <br>
<input type="text" placeholder="Title..." formControlName="title"> <br>
<textarea placeholder="Content..." formControlName="content"></textarea> <br>
<button (click)="updatePost(formGroup.value)">Update</button>
</form>
我尝试这样做只是为了了解 ngrx 中的工作原理。
抱歉问了这么长的问题,但我真的厌倦了这个问题。
问题在这里
if(localStorage.getItem('theSelectedPost') !== null) {
this.store.dispatch(setSelectedPost({post: localStorage.getItem('theSelectedPost')}));
}
您正在从 localStorage 读取 string
,然后将其传递给状态。要修复它,您至少可以 JSON.parse
它,然后再保存到商店。
从 localStorage 读取并写入它听起来是 Effects 的一个很好的用例。我会把这个逻辑放在那里。
我建议您编写更多类型安全的代码来消除此类错误。
我正在使用 angular & ngrx 开发博客。
post从服务器接收,显示在posts-list.component
.
当用户点击 post 标题时,他应该被导航到路线 /posts/:id
以查看完整的 post,并且 post 应该保存在商店和你的 localStorage 为 selectedPost
.
问题是 selectedPost
已在商店和本地存储中设置,但未显示在视图中。
代码如下:(未应用任何样式)
模块:
app.module.ts:
// imports...
@NgModule({
declarations: [
AppComponent,
BaseComponent,
HomeComponent,
NavigationComponent,
FooterComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
HttpClientModule,
StoreModule.forRoot({}),
StoreDevtoolsModule.instrument(),
EffectsModule.forRoot(),
FontAwesomeModule,
PostsModule
],
providers: [AppPreloadingStrategyService],
bootstrap: [AppComponent]
})
export class AppModule { }
posts.module.ts:
// imports..
@NgModule({
declarations: [
PostsListComponent,
PostComponent
],
imports: [
CommonModule,
RouterModule,
ReactiveFormsModule,
StoreModule.forFeature(featureKey,postReducer),
EffectsModule.forFeature([PostEffects])
],
exports: [
PostsListComponent,
PostComponent
]
})
export class PostsModule { }
状态:
post.actions.ts:
// imports...
export const loadPosts = createAction('[Post] Load');
export const loadPostsSuccess = createAction('[Post] Load Success', props<{posts: Post[]}>());
export const loadPostsError = createAction('[Post] Load Error', props<{error: string}>());
export const setSelectedPost = createAction('[Post] Set Selected Post', props<{post: any}>());
export const selectPost = createAction('[Post] Select', props<{post: Post}>());
export const updatePost = createAction('[Post] Update', props<{update: Update<Post>}>());
export const selectUpdatedPost = createAction('[Post] Select Updated', props<{title: string, content: string}>());
post.reducer.ts:
export interface PostsState extends EntityState<Post> {
selectedPost: Post;
error: any;
}
export function selectItemId(a: Post): string {
return a.id.toString();
}
export function sortByName(a: Post, b: Post): number {
return a.title.localeCompare(b.title);
}
export const postAdapter: EntityAdapter<Post> = createEntityAdapter<Post>();
export const initialState = postAdapter.getInitialState({selectId: selectItemId,
sortComparer: sortByName,selectedPost: undefined, error: ''});
export const _postReducer = createReducer(
initialState,
on(postActions.loadPostsSuccess, (state , { posts }) => {
return postAdapter.setAll(posts, state);
}),
on(postActions.loadPostsError, (state, {error}) => {
return {...state, error: error};
}),
on(postActions.updatePost, (state, {update}) => {
return postAdapter.updateOne(update, state);
}),
on(postActions.setSelectedPost, (state, {post}) => {
return {...state, selectedPost: post};
})
);
export function postReducer(state: any, action: Action) {
return _postReducer(state, action);
}
// selectors
export const featureKey = "posts";
const selectPostsFeature = createFeatureSelector<PostsState>(featureKey);
const {selectAll} = postAdapter.getSelectors();
export const getPosts = createSelector(
selectPostsFeature,
selectAll
);
export const getSelectedPost = createSelector(
selectPostsFeature,
(state: PostsState) => state.selectedPost
);
post.effects.ts:
@Injectable()
export class PostEffects {
constructor(private actions$: Actions, private http: HttpClient) {}
loadPosts$ = createEffect(() => this.actions$.pipe(
ofType('[Post] Load'),
mergeMap(() => this.http.get('http://localhost:3000/posts').pipe(
map(posts => ({type: '[Post] Load Success', posts: posts})),
catchError(err => of({type: '[Post] Load Error', error: err}))
))
));
}
组件:
post.component.ts:
@Component({
selector: 'app-post',
templateUrl: './post.component.html',
styleUrls: ['./post.component.scss']
})
export class PostComponent implements OnInit {
constructor(
private store: Store,
private fb: FormBuilder,
private route: ActivatedRoute,) { }
post$: Observable<Post>;
postUrl: any;
formGroup = this.fb.group({
title: [''],
content: ['']
});
ngOnInit(): void {
if(localStorage.getItem('theSelectedPost') !== null) {
this.store.dispatch(setSelectedPost({post: localStorage.getItem('theSelectedPost')}));
}
this.post$ = this.store.pipe(select(getSelectedPost));
this.postUrl = this.route.snapshot.paramMap.get("id");
}
updatePost(post: Post){
this.store.dispatch(updatePost({update: {id: this.postUrl, changes: post}}));
}
}
post.component.html:
<div class="title">
{{(post$ | async)?.title}}
</div>
<div class="content">
{{(post$ | async)?.content}}
</div>
<img [src]="(post$ | async)?.imageSrc">
<div class="categories">
<div class="category" *ngFor="let category of (post$ | async)?.categories">
{{category}}
</div>
</div>
<form [formGroup]="formGroup">
Update Post: <br>
<input type="text" placeholder="Title..." formControlName="title"> <br>
<textarea placeholder="Content..." formControlName="content"></textarea> <br>
<button (click)="updatePost(formGroup.value)">Update</button>
</form>
我尝试这样做只是为了了解 ngrx 中的工作原理。
抱歉问了这么长的问题,但我真的厌倦了这个问题。
问题在这里
if(localStorage.getItem('theSelectedPost') !== null) {
this.store.dispatch(setSelectedPost({post: localStorage.getItem('theSelectedPost')}));
}
您正在从 localStorage 读取 string
,然后将其传递给状态。要修复它,您至少可以 JSON.parse
它,然后再保存到商店。
从 localStorage 读取并写入它听起来是 Effects 的一个很好的用例。我会把这个逻辑放在那里。
我建议您编写更多类型安全的代码来消除此类错误。