NgRx技术问题:如何基于路由参数获取单条记录?
问题:如何通过NgRx Store结合服务获取单条Activity记录?
现状与问题
我之前通过服务直接根据ID获取单条Activity记录,代码如下:
// component.ts public activity$: Observable<Activity> = this._activatedRoute.params.pipe( switchMap((params) => this._activityService.getActivity(params['activityId']) ) );
同时也能正常从NgRx Store中获取所有Activities列表:
// component.ts public activities$: Observable<Activity[]> = this._store.select(getActivities);
我熟悉Vuex,但尝试从Store中获取指定ID的Activity时失败了。尝试的组件代码:
// component.ts public activity$: Observable<Activity | undefined> = this._activatedRoute.params.pipe( switchMap((params) => this._store.pipe( select(getSelectedActivity(params['activityId'])) ) ) );
对应的Reducer代码:
// reducer.ts const activityFeatureState = createFeatureSelector<ActivityState>('activities'); ... export const getSelectedActivityId = createSelector( activityFeatureState, (state) => { return state.selectedActivityId; } ); export const getSelectedActivity = createSelector( activityFeatureState, getSelectedActivityId, (state, selectedActivityId) => { console.log('selectedActivityId', selectedActivityId); // undefined return state.activities.find( (activity) => activity.id === selectedActivityId ); } );
问题出在selectedActivityId始终是undefined,导致无法匹配到对应的Activity。现在希望通过dispatch Effect结合现有服务,实现从Store中获取单条Activity记录的功能。
解决方案:通过Effect+Store实现单条Activity获取
1. 定义相关Action
首先创建用于触发加载、处理结果的Action:
// actions/activity.actions.ts import { createAction, props } from '@ngrx/store'; import { Activity } from '../models/activity.model'; export const loadActivity = createAction( '[Activity] Load Activity', props<{ activityId: string }>() ); export const loadActivitySuccess = createAction( '[Activity] Load Activity Success', props<{ activity: Activity }>() ); export const loadActivityFailure = createAction( '[Activity] Load Activity Failure', props<{ error: any }>() );
2. 编写Effect调用服务
在Effect中监听loadActivity Action,调用现有服务获取数据,并分发结果Action:
// effects/activity.effects.ts import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { of } from 'rxjs'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { ActivityService } from '../services/activity.service'; import * as ActivityActions from '../actions/activity.actions'; @Injectable() export class ActivityEffects { loadActivity$ = createEffect(() => this.actions$.pipe( ofType(ActivityActions.loadActivity), mergeMap((action) => this.activityService.getActivity(action.activityId).pipe( map((activity) => ActivityActions.loadActivitySuccess({ activity })), catchError((error) => of(ActivityActions.loadActivityFailure({ error }))) ) ) ) ); constructor( private actions$: Actions, private activityService: ActivityService ) {} }
3. 更新Reducer处理Action
修改Reducer,处理加载成功的Action,将获取到的Activity存入state,并设置selectedActivityId:
// reducers/activity.reducer.ts import { createReducer, on } from '@ngrx/store'; import * as ActivityActions from '../actions/activity.actions'; import { ActivityState } from '../models/activity.state'; const initialState: ActivityState = { activities: [], selectedActivityId: null, error: null }; export const activityReducer = createReducer( initialState, on(ActivityActions.loadActivitySuccess, (state, { activity }) => ({ ...state, // 存在则更新,不存在则添加 activities: state.activities.some(a => a.id === activity.id) ? state.activities.map(a => a.id === activity.id ? activity : a) : [...state.activities, activity], selectedActivityId: activity.id, error: null })), on(ActivityActions.loadActivityFailure, (state, { error }) => ({ ...state, error })) );
4. 修改Selector支持动态ID查询
创建支持传入ID参数的动态Selector,替代原有依赖state内ID的逻辑:
// selectors/activity.selectors.ts import { createFeatureSelector, createSelector } from '@ngrx/store'; import { ActivityState } from '../models/activity.state'; const activityFeatureState = createFeatureSelector<ActivityState>('activities'); // 保留原有获取所有activities的Selector export const getActivities = createSelector( activityFeatureState, (state) => state.activities ); // 动态传入ID获取指定Activity的Selector export const getActivityById = (activityId: string) => createSelector( activityFeatureState, (state) => state.activities.find(activity => activity.id === activityId) );
5. 组件中Dispatch Action并获取数据
在组件中监听路由参数,dispatch加载Action触发Effect调用服务,再通过动态Selector从Store获取对应ID的Activity:
// component.ts import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { switchMap, tap } from 'rxjs/operators'; import { Activity } from '../models/activity.model'; import * as ActivityActions from '../actions/activity.actions'; import { getActivityById } from '../selectors/activity.selectors'; @Component({ selector: 'app-activity-detail', templateUrl: './activity-detail.component.html' }) export class ActivityDetailComponent implements OnInit { public activity$: Observable<Activity | undefined>; constructor( private _activatedRoute: ActivatedRoute, private _store: Store ) {} ngOnInit(): void { this.activity$ = this._activatedRoute.params.pipe( // 分发加载Action,触发Effect调用服务 tap((params) => this._store.dispatch(ActivityActions.loadActivity({ activityId: params['activityId'] }))), // 动态传入ID,从Store获取对应Activity switchMap((params) => this._store.select(getActivityById(params['activityId']))) ); } }
内容的提问来源于stack exchange,提问作者Damon
相关产品推荐
相关产品推荐

