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

如何实现带自定义名称的NgRX Signal Store可复用Feature

自定义命名的NgRX Signal Store Feature实现

问题描述

需要将NgRX Signal Store中包含loading/loadingError/result的状态与对应的异步方法提取为可自定义名称的signalStoreFeature,实现类似withEntities的命名集合能力,同时支持在方法中注入服务依赖。

完整实现代码

1. 类型定义

import { signalStoreFeature, withState, withMethods, patchState, rxMethod } from '@ngrx/signals';
import { OperatorFunction, pipe, tap, switchMap } from 'rxjs';
import { tapResponse } from '@ngrx/operators';
import { inject } from '@angular/core';

// 基础状态类型
export type StatefulMethodResultState<TResult> = {
  loading: boolean;
  loadingError: string | null;
  result: TResult | null;
};

// 命名后的状态类型,自动生成`${name}State`格式的键
export type NamedStatefulMethodResultState<TResult, TName extends string> = {
  [K in TName as `${K}State`]: StatefulMethodResultState<TResult>;
};

// 方法工厂类型:接收store实例,返回rxMethod所需的操作符管道
export type StatefulMethodFactory<TResult, TParams, TStore> = (
  store: TStore
) => OperatorFunction<TParams, TResult>;

2. Feature核心实现

export const withStatefulMethodResult = <
  TResult,
  TParams,
  TName extends string
>(
  methodFactory: StatefulMethodFactory<TResult, TParams, NamedStatefulMethodResultState<TResult, TName>>,
  name: TName
) => {
  return signalStoreFeature(
    // 初始化对应名称的状态片段
    withState<NamedStatefulMethodResultState<TResult, TName>>({
      [`${name}State`]: {
        loading: false,
        loadingError: null,
        result: null,
      },
    } as NamedStatefulMethodResultState<TResult, TName>),
    // 生成对应名称的异步方法
    withMethods((store: NamedStatefulMethodResultState<TResult, TName>) => ({
      [name]: rxMethod<TParams>(pipe(methodFactory(store))),
    }))
  );
};

3. 使用示例

// 假设的集合类型
interface MyCollection {
  id: number;
  name: string;
}

// 定义Store
export const MyCollectionsStore = signalStore(
  withStatefulMethodResult<MyCollection[], { page: number; itemsPerPage: number }, 'loadPage'>(
    (store) => {
      // 直接在方法工厂内注入依赖(withMethods回调处于Angular注入上下文)
      const collectionsApiService = inject(CollectionsService);
      
      return pipe(
        tap(() => 
          patchState(store, { 
            loadPageState: { loading: true, loadingError: null } 
          })
        ),
        switchMap(({ page, itemsPerPage }) => 
          collectionsApiService.getMyCollections(page, itemsPerPage).pipe(
            tapResponse({
              next: (result) => 
                patchState(store, { loadPageState: { result } }),
              error: (e) => 
                patchState(store, { loadPageState: { loadingError: `${e}` } }),
              finalize: () => 
                patchState(store, { loadPageState: { loading: false } }),
            })
          )
        )
      );
    },
    'loadPage'
  )
);

// 组件中使用Store
const store = inject(MyCollectionsStore);
// 调用异步方法
store.loadPage({ page: 1, itemsPerPage: 10 });
// 访问对应状态
const isLoading = store.loadPageState.loading();
const collections = store.loadPageState.result();

关键说明

  • 类型安全:通过泛型约束确保状态键与方法名称严格对应,避免拼写错误
  • 依赖注入:利用Angular的inject函数直接在方法工厂内获取服务,完全符合Angular依赖注入规范
  • rxMethod封装:自动将用户定义的操作符管道包装为rxMethod,保持Signal Store的异步方法风格
  • 状态隔离:每个命名方法对应独立的${name}State状态片段,避免不同业务逻辑的状态互相污染

内容的提问来源于stack exchange,提问作者Emanuel Vintilă

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 17:53:19