Angular Ionic中SignalStore的rxMethod注入上下文错误求助
Angular Ionic独立组件中使用@ngrx/signals SignalStore报NG0203错误
开发Angular Ionic独立组件应用时,使用@ngrx/signals的SignalStore遇到报错,错误提示为NG0203:rxMethod()只能在注入上下文(如构造函数、工厂函数、字段初始化器或使用runInInjectionContext的函数)中使用。
该SignalStore在常规Angular应用中可正常运行,但在当前Ionic项目中,通过FolderPage动态懒加载GroupComponent时无法正常注入使用(已在FolderPage的providers数组中声明ShoppingListStore)。
ShoppingListStore代码
import { patchState, signalStore, signalStoreFeature, type, withComputed, withHooks, withMethods, withState, } from '@ngrx/signals'; import { HttpShoppingListService } from '../service/http-shopping-list.service'; import { computed, inject } from '@angular/core'; import { rxMethod } from '@ngrx/signals/rxjs-interop'; import { debounceTime, distinctUntilChanged, pipe, switchMap, tap } from 'rxjs'; import { tapResponse } from '@ngrx/operators'; import { ShoppingList } from '../domain/ShoppingList'; import { ShoppingListToCreate } from '../domain/ShoppingListToCreate'; import { Product } from '../domain/Product'; import { ShoppingListToUpdate } from '../domain/ShoppingListToUpdate'; export type Filter = { shoppingListId: string; order: 'asc' | 'desc'; }; export type ShoppingListState = { shoppingLists: ShoppingList[]; isLoading: boolean; filter: Filter; }; export const initialState: ShoppingListState = { shoppingLists: [], isLoading: false, filter: { shoppingListId: '', order: 'asc' }, }; export const ShoppingListStore = signalStore( { providedIn: 'root' }, withState(initialState), // withComputed((store) => ({ // shoppingListCount: computed(() => store.shoppingLists().length), // sortedShoppingLists: computed(() => store.shoppingLists().sort((a, b) => a.name.localeCompare( b.name ))), // isLoading: computed(() => store.isLoading()), // })), withMethods( (store, httpShoppingListService = inject(HttpShoppingListService)) => ({ loadShoppingListsByGroupId: rxMethod<string>( pipe( debounceTime(1000), distinctUntilChanged(), tap(() => patchState(store, { isLoading: true })), switchMap((groupId: string) => httpShoppingListService.getAllShoppingLists(groupId).pipe( tapResponse({ next: (shoppingLists: ShoppingList[]) => patchState(store, { shoppingLists }), error: (err) => { console.error(err); }, finalize: () => patchState(store, { isLoading: false }), }) ) ) ) ), addShoppingList: rxMethod<ShoppingListToCreate>( pipe( debounceTime(300), tap(() => patchState(store, { isLoading: true })), switchMap(shoppingListToCreate => httpShoppingListService.addShoppingList(shoppingListToCreate).pipe( tapResponse({ next: (createdShoppingList: ShoppingList) => { const test:ShoppingList[] = store.shoppingLists(); const updatedShoppingLists: ShoppingList[] = [ ...test, createdShoppingList, ] patchState(store, { shoppingLists: updatedShoppingLists }); }, error: (err) => console.error(err), finalize: () => patchState(store, { isLoading: false }), }) ) ) ) ), addProductsToShoppingList: rxMethod<{ shoppingListId: string; products: Product[]; }>( pipe( distinctUntilChanged(), tap(() => patchState(store, { isLoading: true })), switchMap(({ shoppingListId, products }) => httpShoppingListService .addProductsToShoppingList(shoppingListId, products) .pipe( tapResponse({ next: (updatedShoppingList: ShoppingList) => { patchState(store, ({ shoppingLists }) => ({ shoppingLists: shoppingLists.map((list) => list.id === shoppingListId ? updatedShoppingList : list ), })); }, error: (err) => console.error(err), finalize: () => patchState(store, { isLoading: false }), }) ) ) ) ), updateShoppingList: rxMethod<{ shoppingListToUpdate: ShoppingListToUpdate; }>( pipe( distinctUntilChanged(), tap(() => patchState(store, { isLoading: true })), switchMap(({ shoppingListToUpdate }) => httpShoppingListService .updateShoppingList(shoppingListToUpdate) .pipe( tapResponse({ next: (updatedShoppingList: ShoppingList) => { patchState(store, ({ shoppingLists }) => ({ shoppingLists: shoppingLists.map((list) => list.id === shoppingListToUpdate.id ? updatedShoppingList : list ), })); }, error: (err) => { console.error(err); }, finalize: () => patchState(store, { isLoading: false }), }) ) ) ) ), }) ), );
GroupComponent代码
import { Component, inject, OnInit, signal, Signal } from '@angular/core'; import { IonicModule, NavController } from '@ionic/angular'; import { ShoppingListStore } from '../../store/shoppingListStore'; @Component({ selector: 'app-group', templateUrl: './group.component.html', styleUrls: [ './group.component.scss' ], imports: [ IonicModule ], standalone: true, }) export class GroupComponent { readonly shoppingListStore = inject(ShoppingListStore); // activeIndex = 0; protected readonly groupId: Signal<string> = signal('1'); // protected readonly shoppingLists: Signal<ShoppingList[]>; // protected readonly isLoading: Signal<boolean> = this.shoppingListStore.isLoading // protected readonly count: Signal<number> = this.shoppingListStore.shoppingListCount constructor() { this.shoppingListStore.loadShoppingListsByGroupId('1') // this.shoppingListStore.loadShoppingListsByGroupId('1'); // this.shoppingLists = this.shoppingListStore.sortedShoppingLists; // @ts-ignore // this.a = at; // @ts-ignore } }
FolderPage及SideBarContentService代码
import { Component, inject, OnInit, signal } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { IonButtons, IonContent, IonHeader, IonMenuButton, IonTitle, IonToolbar } from '@ionic/angular/standalone'; import { NgComponentOutlet, NgIf } from '@angular/common'; import { ComponentMapping } from '../domain/component-mapping'; import { SideBarContentService } from '../service/side-bar-content.service'; import { Folder } from '../domain/Folder'; import { ShoppingListStore } from '../store/shoppingListStore'; @Component({ selector: 'app-folder', templateUrl: './folder.page.html', styleUrls: [ './folder.page.scss' ], standalone: true, providers: [ShoppingListStore], imports: [ IonHeader, IonToolbar, IonButtons, IonMenuButton, IonTitle, IonContent, NgComponentOutlet, NgIf ], }) export class FolderPage implements OnInit { private activatedRoute = inject(ActivatedRoute); private sideBareContentService = inject(SideBarContentService); private allFolders = this.sideBareContentService.folders; currentFolderPage = signal<Folder | null>(null); component = signal<any | null>(null); private componentMapping: ComponentMapping = this.sideBareContentService.componentMapping; ngOnInit() { const folderId = this.activatedRoute.snapshot.paramMap.get('id'); if (folderId) { const currentFolder = this.allFolders.find(folder => folder.id === folderId); if (currentFolder) { this.setFolder(currentFolder); } } } private setFolder(folder: Folder) { this.currentFolderPage.set(folder); this.loadComponent(folder); } private loadComponent(folder: Folder | null) { if( folder && this.componentMapping[ folder.id ] ) { this.componentMapping[ folder.id ]().then((component: any) => { this.component.set(component); }); } else { this.component.set(null); } } } import { AppPage } from '../domain/AppPage'; import { Observable, of } from 'rxjs'; import { ComponentMapping } from '../domain/component-mapping'; import { Folder } from '../domain/Folder'; import { DashboardComponent } from '../components/dashboard/dashboard.component'; import { GroupComponent } from '../components/group/group.component'; export class SideBarContentService { constructor() { } private _appPages: AppPage[] = [ { sideBarName: 'Dashboard', url: '/folder/dashboard', icon: 'home-outline', isVisibleForUser: true }, { sideBarName: 'Group Shopping Lists', url: '/folder/group', icon: 'cart-outline', isVisibleForUser: true }, { sideBarName: 'PasswordManager', url: '/folder/password-manager', icon: 'key-outline', isVisibleForUser: true }, ]; private _componentMapping: ComponentMapping = { 'dashboard': () => import('src/app/components/dashboard/dashboard.component').then(m => m.DashboardComponent), 'group': () => import('src/app/components/group/group.component').then(m => m.GroupComponent), }; private _folders: Folder[] = [ { id: 'dashboard', title: 'Home', component: DashboardComponent, }, { id: 'group', title: 'Shopping Lists for Group', component: GroupComponent, } ]; get appPages(): Observable<AppPage[]> { return of(this._appPages); } get folders(): Folder[] { return this._folders; } get componentMapping() : ComponentMapping { return this._componentMapping; } }
错误栈信息
ERROR RuntimeError: NG0203: rxMethod() can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`. Find more at https://angular.io/errors/NG0203 at assertInInjectionContext (core.mjs:3324:11) at rxMethod (ngrx-signals-rxjs-interop.mjs:5:5) at shoppingListStore.ts:50:35 at ngrx-signals.mjs:211:21 at ngrx-signals.mjs:87:62 at Array.reduce (<anonymous>) at new _SignalStore (ngrx-signals.mjs:87:35) at Object.SignalStore_Factory [as factory] (ngrx-signals.mjs:116:14) at core.mjs:3136:35 at runInInjectorProfilerContext (core.mjs:867:5) handleError @ core.mjs:7195 next @ core.mjs:33183 ConsumerObserver2.next @ Subscriber.js:90 Subscriber2._next @ Subscriber.js:59 Subscriber2.next @ Subscriber.js:32 (anonym) @ Subject.js:41 errorContext @ errorContext.js:23 Subject2.next @ Subject.js:31 emit @ core.mjs:6590 (anonym) @ core.mjs:7082 invoke @ zone.js:339 run @ zone.js:110 runOutsideAngular @ core.mjs:6944 onHandleError @ core.mjs:7082 handleError @ zone.js:342 runGuarded @ zone.js:124 api.microtaskDrainDone @ zone.js:2235 drainMicroTaskQueue @ zone.js:541 invokeTask @ zone.js:444 invokeTask @ zone.js:1086 globalCallback @ zone.js:1116 globalZoneAwareCallback @ zone.js:1147 22 weitere Frames anzeigen Weniger anzeigen
问题
我确认rxMethod的实现是正确的,请问问题原因是什么?
内容的提问来源于stack exchange,提问作者Spring
相关产品推荐
相关产品推荐

