模板中未显示 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 的一个很好的用例。我会把这个逻辑放在那里。

我建议您编写更多类型安全的代码来消除此类错误。