NGRX Entity如何在Angular模板中正确遍历渲染实体数据
问题根因
selectEntities 选择器返回值类型为Dictionary<Movie>,是以实体ID为键、实体对象为值的普通键值对对象,并非可遍历的数组。Angular的*ngFor指令仅支持遍历数组、实现了可迭代协议的对象,直接遍历普通对象就会触发运行错误。
最佳实践方案
NgRX Entity的adapter已经内置了返回有序实体数组的记忆化选择器selectAll,不需要自行编写map转换逻辑,直接使用即可,性能最优。
1. 调整Selector导出
在Reducer的selector定义文件中,导出基于selectAll的全量列表选择器:
const { selectIds, selectEntities, selectAll, selectTotal } = fromReducer.adapter.getSelectors(); export const getMovieState = createFeatureSelector<fromReducer.State>(fromReducer.moviesFeatureKey); // 原有单条实体查询用选择器保留,适合根据ID快速取单条数据的场景 export const selectMovieEntities = createSelector(getMovieState, selectEntities); // 新增:全量电影数组选择器 export const selectAllMovies = createSelector(getMovieState, selectAll);
2. 调整组件代码
修正流的类型声明,将数据加载动作迁移到ngOnInit生命周期(符合Angular最佳实践):
import { Component, OnInit } from '@angular/core'; import { Store, select } from '@ngrx/store'; import { Observable } from 'rxjs'; // 路径根据实际项目调整 import { loadMovies } from './store/movie.actions'; import { selectAllMovies } from './store/movie.reducer'; import { Movie } from './models/movie.model'; import { MovieState } from './store/movie.state'; @Component({ selector: 'app-movies', templateUrl: './movies.component.html', styleUrls: ['./movies.component.scss'] }) export class MoviesComponent implements OnInit { // 类型修正为Movie数组 movies$: Observable<Movie[]>; constructor(private store: Store<MovieState>) { this.movies$ = this.store.pipe(select(selectAllMovies)); } ngOnInit(): void { this.store.dispatch(loadMovies()); } }
3. 修正模板代码
修复原模板中table结构不合法的问题,用as语法复用async订阅结果,避免重复订阅:
<h3>MOVIES</h3> <table *ngIf="movies$ | async as movies"> <thead> <tr> <th>序号</th> <th>电影名称</th> </tr> </thead> <tbody> <tr *ngFor="let movie of movies; index as i"> <td>{{ i + 1 }}</td> <td>{{ movie.title }}</td> </tr> </tbody> </table>
备选方案(非必要不使用)
如果有特殊场景必须基于selectEntities的返回值处理,可以通过RxJS的map操作符自行转成数组,但该方案丢失了内置选择器的缓存优化,性能弱于直接使用selectAll:
import { map } from 'rxjs/operators'; // 组件内赋值流的逻辑修改为 this.movies$ = this.store.pipe( select(selectMovieEntities), map(entities => Object.values(entities)) );
注意事项
- 不要在模板中对同一个流多次使用
async管道,会触发多次订阅产生不必要的性能开销,推荐使用*ngIf="流 | async as 变量"的写法,一次订阅即可复用值,同时自带空值判断 - NgRX Entity的四个内置选择器都做了记忆化缓存,优先使用官方提供的选择器:
selectAll用于渲染列表、selectEntities用于根据ID快速查询单条实体、selectTotal获取总数、selectIds获取有序ID列表,不要重复编写转换逻辑 - 原模板HTML结构不符合规范:
<tr>标签的直接子元素只能是<td>或<th>,不能直接嵌套<li>,如果需要列表样式直接使用<ul>+<li>结构即可,不需要套table。
内容的提问来源于stack exchange,提问作者EchtFettigerKeks
相关产品推荐
相关产品推荐

