You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We've recently migrated our app's state management from @ngrx/store to @ngrx/signals, and it's been great. However, there's one pain point that we keep encountering, and it appears other teams in our org and also some GitHub users have been running into it as well.
Issue
We commonly use optional or undefined state in our app, mostly in cases where data is not immediately available. As others have mentioned, this poses an issue with signal stores, as deep signals can't be made from undefined state:
typeExampleState={user?: {name: string;email: string;};userLoaded: boolean;};constExampleStore=signalStore(withState<ExampleState>({userLoaded: false}));// ...inject(ExampleStore).user.name();// Throws, as `user` is undefined
The same issue also applies to indexed types. (@ngrx/signals/entities exists for this use-case but is overkill a lot of the time.)
typeExampleState={users: Record<string,{name: string;email: string;}>;};constExampleStore=signalStore(withState<ExampleState>({users: {}}));// ...inject(ExampleStore).users[id].name();// Throws, as `users[id]` is undefined
Our workaround for this at the moment has been to invoke the signal before doing optional chaining, the obvious downside of which is losing the benefit of signals.
These limitations are due to the fact that toDeepSignal returns a regular signal instead of a DeepSignalwhen its value isn't a record at the time of creation.
I wrote a naive alternative implementation that acts more like building a lens over a state slice using optional chaining, shown below.
The main differences in this approach are:
DeepSignal won't take the value of the property into account, so accessing a property on a DeepSignal will always return another DeepSignal
Means that nullable properties always return DeepSignal, but also means that any arbitrary property access returns a DeepSignal too.
Keys won't be enumerable since they aren't defined on the target Signal
functiontoDeepSignal<T>(signal: Signal<T>): DeepSignal<T>{constcache={};returnnewProxy(signal,{get(target,prop){// For Angular's `SIGNAL` symbol, there's probably a nicer way to do thisif(typeofprop==='symbol'){returntarget[prop];}return(cache[prop]??=toDeepSignal(computed(()=>target()?.[prop])));}})asDeepSignal<T>;}
Questions that I want to bring up:
What's the rationale behind the current approach?
What are the shortcomings with my naive approach?
What's the intended way to handle nullable state objects (asynchronously loaded or otherwise) using signal stores?
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
We've recently migrated our app's state management from
@ngrx/store
to@ngrx/signals
, and it's been great. However, there's one pain point that we keep encountering, and it appears other teams in our org and also some GitHub users have been running into it as well.Issue
We commonly use optional or undefined state in our app, mostly in cases where data is not immediately available. As others have mentioned, this poses an issue with signal stores, as deep signals can't be made from undefined state:
The same issue also applies to indexed types. (
@ngrx/signals/entities
exists for this use-case but is overkill a lot of the time.)Our workaround for this at the moment has been to invoke the signal before doing optional chaining, the obvious downside of which is losing the benefit of signals.
Discussion
These limitations are due to the fact that toDeepSignal returns a regular signal instead of a
DeepSignal
when its value isn't a record at the time of creation.I wrote a naive alternative implementation that acts more like building a lens over a state slice using optional chaining, shown below.
The main differences in this approach are:
DeepSignal
won't take the value of the property into account, so accessing a property on aDeepSignal
will always return anotherDeepSignal
DeepSignal
, but also means that any arbitrary property access returns aDeepSignal
too.Signal
Questions that I want to bring up:
Related to #4305
Beta Was this translation helpful? Give feedback.
All reactions