如何实现带自定义名称的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ă
相关产品推荐
相关产品推荐

