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

Angular中从NgRx Reducer获取数据的问题排查

NgRx多Feature Reducer创建与嵌套数据访问问题

在Angular项目中使用NgRx管理应用状态,分别为管理员用户、所属企业、调研类型创建了多个Reducer。最初尝试多次调用StoreModule.forFeature时出错,无论指定哪个featureKey,系统始终尝试访问“管理员用户”的featureKey。改用ActionReducerMap映射Reducer后,数据成功存入Store,但出现多层嵌套的admins属性,尝试通过admin.admins访问时因数据模型不匹配报错。需要了解如何正确创建多个featureReducer,以及如何处理多层嵌套的数据访问。


相关代码示例

admin.actions.ts

import { createAction, props } from '@ngrx/store';
import { Admin } from "../../components/models";

export const GET_ADMIN = createAction('[ADMIN PAGE] Init');

export const GET_ADMINS_SUCCESS = createAction(
  '[ADMIN PAGE] Admins Success',
  props<{ admins: Admin[] }>()
);

export const GET_ADMINS_ERROR = createAction(
  '[ADMIN PAGE] Admins Error',
  props<{ error: string }>()
);

admin.effects.ts

import { Injectable } from "@angular/core";
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { catchError, map, of, exhaustMap } from 'rxjs';
import { AdminService } from '../../services/admin.service';
import { GET_ADMIN, GET_ADMINS_ERROR, GET_ADMINS_SUCCESS } from '../actions/admin.actions';

@Injectable()
export class AdminsEffect {
  constructor(
    private actions$: Actions,
    private adminService: AdminService,
  ) {}

  loadAllAdmins$ = createEffect(() =>
    this.actions$.pipe(
      ofType(GET_ADMIN),
      exhaustMap((initAction) => {
        return this.adminService.getAllAdmins().pipe(
          map(admins => GET_ADMINS_SUCCESS({admins})),
          catchError(() => of(GET_ADMINS_ERROR({error: "There was an error getting the admins"}))),
        );
      })
    )
  )
};

admin.reducers.ts

import { Action, createReducer, on } from "@ngrx/store";
import { Admin } from "../../components/models/Admin";
import { GET_ADMINS_SUCCESS } from "../actions/admin.actions";

export interface AdminState {
  admins: Admin[];
}


const initialAdminState: AdminState = {
  admins: [],
};

export const adminReducer = createReducer(
    initialAdminState,
    on(GET_ADMINS_SUCCESS, (state, {admins}) => ({
      ...state,
      admins,
    }))
);

admin.selectors.ts

import { createFeatureSelector, createSelector } from '@ngrx/store';
import { adminFeatureKey } from '../admin.state';
import { AdminState } from '../reducers/admin.reducers';

export const selectAdmins = createFeatureSelector<AdminState>(adminFeatureKey);

export const admins = createSelector(
  selectAdmins,
  (selectAdmins) => selectAdmins.admins
)

admin.state.ts

import { ActionReducerMap } from '@ngrx/store';
import { AdminState, adminReducer } from './reducers/admin.reducers';

export interface AdminFlowState {
  admins: AdminState;
}

export const reducers: ActionReducerMap <AdminFlowState> = {
  admins: adminReducer
}

export const adminFeatureKey = "adminState"

admin.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from 'src/environments/environment';
import { CreateAdmin } from '../components/interfaces/CreateAdmin';
import { Admin } from "../components/models/Admin";
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AdminService {

  serverURL = environment.serverURL;

  constructor(private http: HttpClient) { }

  getAllAdmins(): Observable<Admin[]> {
    return this.http.get<Admin[]>(`${this.serverURL}/admins`)
  }
  ...
}

Admin.ts

export interface Admin {
  code: string;
  name: string;
  fsbs: boolean;
  createdIp: string;
  createdAt: Date;
  updatedIp: string;
  updatedAt: Date;
  logo?: Blob;
  pwd?: string;
}

admin.module.ts

...
imports: [
    CommonModule,
    MaterialModule,
    NgxChartsModule,
    AdminRoutingModule,
    StoreModule.forFeature(adminFeatureKey, reducers),
    EffectsModule.forFeature([AdminsEffect]),
    ReactiveFormsModule,
    FormsModule
  ],
...

admins-table.component.ts

import { Component, ViewChild } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { MatSort } from '@angular/material/sort';
import { Admin } from "../models/Admin";
import { select, Store } from '@ngrx/store';
import { admins } from '../../store/selectors/admin.selectors';
import { GET_ADMIN } from '../../store/actions/admin.actions';

@Component({
  selector: 'app-admins-table',
  templateUrl: '../views/admins-table.component.html',
  styleUrls: ['../styles/admins-table.component.scss']
})
export class AdminsTableComponent {
  columnas: string[] = ['code', 'name', 'fsbs', 'createdIp', 'createdAt', 'updatedIp', 'updatedAt', 'logo', 'edit'];
  admins: Admin[] = [];
  dataSource: any;

  @ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
  @ViewChild(MatSort, { static: true }) sort!: MatSort;

  constructor(private store: Store) {}

  ngOnInit() {
    this.store.pipe(select(admins))
    .subscribe(admin => {
      console.log(admin);
      this.store.dispatch(GET_ADMIN());
      this.admins = admin;
      this.dataSource = new MatTableDataSource<Admin>(this.admins);
      this.dataSource.paginator = this.paginator;
      this.dataSource.sort = this.sort;
    });
  }

  filter(event: Event) {
    const filtro = (event.target as HTMLInputElement).value;
    this.dataSource.filter = filtro.trim().toLowerCase();
  }
}

控制台输出

{
  "admins": [
    ...
    {
      "code": "002",
      "name": "Manuel",
      "pwd": "...",
      "fsbs": false,
      "createdIp": "157.100.75.247",
      "createdAt": "2022-09-07T23:08:08.310Z",
      "updatedIp": "157.100.75.247",
      "updatedAt": "2022-09-07T23:08:08.310Z",
      "logo": "..."
    },
    ...
  ]
}

解决方案

1. 修复多层嵌套数据的访问问题

当前出现{ admins: [...] }嵌套的核心原因是:AdminFlowState中定义了admins: AdminState,而AdminState本身又包含admins: Admin[]字段,导致选择器返回的数据结构与预期的Admin[]不匹配。

方案一:简化状态结构

修改admin.state.ts,移除不必要的嵌套层级,直接将adminReducer作为feature的reducer:

// admin.state.ts
import { AdminState, adminReducer } from './reducers/admin.reducers';

export const adminFeatureKey = "adminState";
export const adminReducerMap = adminReducer;

然后在admin.module.ts中更新注册代码:

StoreModule.forFeature(adminFeatureKey, adminReducerMap),

方案二:调整选择器路径

如果需要保留当前的ActionReducerMap结构,修改选择器以匹配嵌套层级:

// admin.selectors.ts
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { adminFeatureKey, AdminFlowState } from '../admin.state';

export const selectAdminFlow = createFeatureSelector<AdminFlowState>(adminFeatureKey);
export const selectAdminState = createSelector(selectAdminFlow, state => state.admins);
export const admins = createSelector(selectAdminState, state => state.admins);

修改后,admins选择器将正确返回Admin[]类型的数据,组件订阅后不会再出现类型不匹配报错。

2. 正确创建多个Feature Reducer

为不同业务模块(管理员、企业、调研类型)创建独立的feature reducer,需遵循以下步骤:

  • 每个模块独立创建状态文件、reducer、actions、selectors
  • 在各自的feature模块中调用StoreModule.forFeature(featureKey, reducer)注册,确保每个feature的featureKey唯一
  • 各模块的effects通过EffectsModule.forFeature独立注册

以企业模块为例:

  1. 创建enterprise.state.ts:
import { EnterpriseState, enterpriseReducer } from './reducers/enterprise.reducers';

export const enterpriseFeatureKey = "enterpriseState";
  1. 在enterprise.module.ts中注册:
imports: [
  ...
  StoreModule.forFeature(enterpriseFeatureKey, enterpriseReducer),
  EffectsModule.forFeature([EnterpriseEffects]),
  ...
]

若需在同一模块中注册多个feature,可通过ActionReducerMap批量注册,但需保证每个key对应独立的reducer:

StoreModule.forFeature('businessModules', {
  adminState: adminReducer,
  enterpriseState: enterpriseReducer,
  surveyTypeState: surveyTypeReducer
})

更推荐的做法是每个feature模块独立注册自身的reducer,保持代码模块化和解耦。


内容的提问来源于stack exchange,提问作者Manuel Pereira Maldonado

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 03:30:48