RFC: Dispatch an action when a selector evaluates to undefined #3456
david-shortman
started this conversation in
Ideas
Replies: 1 comment 2 replies
-
@david-shortman , did your proopsal got approved and implemented? I'm using ngrx version 17* and when I tried to write an action for selectUser.notDefined it is throwing an error. Or, Did you get it worked in any different way to listen for undefined selector effect?. |
Beta Was this translation helpful? Give feedback.
2 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Problem
A frequent problem my coworkers encounter is answering "how do I hydrate the state?" when selecting data that should come from a server.
This problem is answered in a variety ways depending on the context, each with weaknesses.
Data needed by a few components in similar area
Dispatch "component initialized" action
We dispatch an action like "X Component Loaded", and trigger an effect(s) that loads relevant data. We directly select state in the components/services with
store.select
.Weakness
This pattern does not work for sharing state across many libraries in a monorepo, because the effect would need to listen to a potentially large list of actions. for instance, to select user data in a variety of places, the
loadUser$
effect would have to know about every component in which user data was needed.Data needed by many components across different areas of app/ monorepo libraries
canActivate
guards and resolversWe've used either a "guard" class in the
canActive
routing config, or a "resolver" class in theresolvers
routing config to dispatch an action that triggers an effect(s) that loads needed data. We directly select state in the components/services withstore.select
in this case, too.Weakness
This solves the sharing problem, but makes the relationship between selectors and hydration triggers too decoupled. Developers who want to load user data have to know both to use the
selectUser
selector and to put a class in a special place in the router config.Service wrapper
When state isn't used to compose many other selectors, we create a service with a method like "getData" that dispatches an action like "getData Called" which triggers an effect that loads the data if it is not present, and then return "store.select(selectData)". then components and other services can compose Observables of data from there without needing to know the data came from the store
Weakness
This solves the sharing problem and the "too decoupled" problem, but restricts us to composing new data streams only as Observables in services/components and not as selectors, which means we loose out on the composability of selectors and free memoization benefits
Other "bad" approach
When not following good action hygiene, we dispatch an action to trigger loading the data, then immediately call
store.select
Weakness
Besides not following good action hygiene by reusing an action, this also has a developer experience problem of requiring two steps in order to load data. Also, if the selector is dependent on some data composed from another selector, then we might be dispatching an action to get some underlying data which means we know about the internals of the selector.
Proposal
Given the difficulty and problems of trying to hydrate state when it is needed, I've developed a way inside my organization to detect when selectors are called but have no data, and react to that event.
That approach can be adapted into NgRx with an API that looks something like the following.
Say I have a selector
selectUser
that returns information about a user like so:When a part of the application wants to reference the current user in a component or service, it uses
store.select(selectUser)
. When composing another selector that depends on user data, the selector is used directly increateSelector
.What if we could dispatch an action that signalled that a selector had no value when it was invoked?
For instance, what if my
loadUser$
effect could look something like this?The
notDefined
action would only be available for a selector declared like so:Ignoring how the internals of NgRx could be implemented to perform this, the API solves many problems typically encountered by developers when trying to hydrate the state at the right moment in their app:
Beta Was this translation helpful? Give feedback.
All reactions