After updating to Angula v17, I am rewriting a legacy component using the "classic" ngrx (observables based) with the new ngrx/signals store.
I defined some selectors in the following fashion:
withComputed((state) => ({
selectOrderId: computed(() => state.orderId()),
selectAvailableAgents: computed(() => state.availableAgents())
...
})
However, for the lazy nature of signals, if I use these selectors in the component class (not template), they will not be updated dynamically if the value changes:
const orderId = this.ordersStore.selectOrderId();
// orderId has only the current (at the time of invocation) value from the store
If orderId is null, I need to invoke a service to fetch it, return the value to the component and update the store as well. Eventually, to keep this logic together, I can implement this logic inside the withMethod that returns returns the value as an observable.
This way I would skip the selector call altogether:
withMethods((state, ordersService = inject(OrdersService)) => ({
fetchOrderId$: () => {
const orderId = state.orderId();
// If orderId available, do not call the API
if (orderId) {
return of(orderId);
}
return ordersService.getOrderId()
.pipe(
tapResponse({
next: (orderId) => {
patchState(state, { orderId })
},
error: (error: HttpErrorResponse) => {
console.error("An error occurred: ", error.message);
}
})
)
}
//In component class:
this.orderStore.fetchOrderId$()
.pipe(
takeUntil(this.destroy$)
)
.subscribe({
next: (orderId) => {
// Here the logic using orderId
}
)}
Using this approach, though, seems to defeat a bit the way signals work.
In this scenario, I could think of effect() as the only way to indirectly detect when a specific part of the signal state is changed and apply specific actions to the component, for instance. But Angular docs suggest not using them to propagate the state, to avoid potential issues.
Therefore the resulting approach loses the peculiar indirectness provided by ngrx with observables:
- Get the value with a selector (computed signal)
- If value is not available in the store, invoke a service and:
--> return it to the caller (component)
--> update the store (patchState)
For the cases where a value is not needed immediately, I will use rxMethods to update the state and re-execute if their params change.
Is this a valid approach to managing selectors and values with ngrx signals or am I missing anything?
Just to make sure that you didn't make the same mistake I made: When you select a signal from the signal store, do not call it (i.e. add
()). If you do so, the signal is only evaluated once and you don't have the handle of the signal but the value of whatever was in that signal. You should only call/ evaluate the signal when you really want the value. For example in your html, if you want to display the current value