Reputation: 11
Hi @all I have a Angular Ionic app and in this application my signalstore does make a lot of Problems:
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 }),
})
)
)
)
),
})
),
);
It can not be inject in my component:
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
}
}
In a normal angular Application, this store works but i dont know why its throw an error. Maybe because a module import ?:
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;
}
}
Following Error message occours:
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
I think the rxMethod is being implemented correctly... Does anyone have any ideas?
Upvotes: 0
Views: 122
Reputation: 3
From the docs:
By default, the rxMethod needs to be executed within an injection context. It's tied to its lifecycle and is automatically cleaned up when the injector is destroyed.
Initialization of the reactive method outside an injection context is possible by providing an injector as the second argument to the rxMethod function.
Your not using the rxMethod()
in the right context.
The solution is to inject the current Injector
and pass it to the
rxMethod()
withMethods(
(store, httpShoppingListService = inject(HttpShoppingListService),injector = inject(Injector)//inject the current injector) => ({
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 }),
})
)
)
,{ injector})//pass the injector to the rx method
),
Upvotes: 0
Reputation: 11
Provide ShoppingListStore in your component
providers: [ShoppingListStore],
Upvotes: 0