You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 20:01:10