When should I use NgRx or Redux?
"When should I use NgRx" is a question whose answers have changed several times over the years, and personally I find a lot of the guidance available online to be a little too "does it feel right" and “can something else handle what you are doing”. While informative and a great way to learn about some new libraries, this is largely not helpful for deciding when to say “yes, I would benefit from using NgRx for this project”. Over the last several years I’ve tried to come up with a mental framework for deciding whether or not a project would benefit from NgRx, and decided to share what I’ve come up with and see what others think.
Obviously this is subjective and I would love to hear what others have to say and see what the community would add or remove to this list!
Note that while this article references NgRx, this approach applies equally well to Redux in React for the most part.
The Conditions
In my experience, NgRx will prove to be a valuable addition to a project if…
- There is enough information coming back from the server(s) that the frontend can effectively model state, and that the frontend has at least some non-trivial state beyond storing what the server(s) respond with.
- There is a substantial amount of state that cannot be cleanly made the responsibility of some component. This includes the usage of component-specific Angular services.
- State can be modeled with little-to-no ambiguity, without including detailed knowledge of which specific set of components are being rendered.
Let’s talk about each of these in greater depth.
There is enough information coming back from the server
This is largely to set a precondition that you should avoid trying to use NgRx in situations where the API is handling all or most of the state for the frontend. If your UI only needs to know about the { data, error, loading }
states of API calls for the most part (like when using Apollo or rx-query), then chances are NgRx isn’t a great option.
This is due to how NgRx handles all state the same way regardless of source. Phrased differently, NgRx does not care if your state comes from an HTTP call, a complex set of multi-step user interactions, a simple form, or a multi-page complex form that can save partial progress. As such, NgRx is not a great tool for handling well defined state like that of an HTTP call since that is something so ubiquitous and well defined that it is almost always worth using a library that is specifically aware of API call state. The benefits of these tools are the simplicity they provide specifically because they are aware of the source of the data/state that they handle.
There is a substantial amount of state that cannot be cleanly made the responsibility of some component
Many popular frontend libraries these days are component based, and components are pretty good at handling state for their own little area of the HTML on a page. Further, many libraries supporting functionality like forms, modals, API calls and the like are quite good at managing the state of their respective features, often to the point where they make it trivial to handle state nearby to where it is actually being used.
Of course, sometimes this complexity still adds up to be way more than you want in a single component, and there might not be a good way to break that component up that you and your team are happy with. In these situations I personally reach first for component specific services, sometimes even multiple per feature of the app. This way the component can focus on the UI state, and act as a convenient mounting point for the logic (e.g. form validations, HTTP calls, anything else non-trivial) from use-case-specific services. This keeps everything “in the neighborhood” in which it is actually used, but still builds in a large amount of flexibility and abstraction.
State can be modeled with little-to-no ambiguity
This condition is perhaps the part I see mentioned the least in other articles and literature around NgRx, but to me is one of the most important parts of deciding if NgRx is right for your application.
This condition becomes hard to meet when an application cannot guarantee certain properties or behaviors are present and available in all situations. What if they are optional based on runtime parameters, but they are required in some situations? For example, consider a situation where, when one feature is turned on, then a second must also be present, but otherwise the second feature is optional; how do we define state for these two features? What does this mean for default/initial state? What happens in the components using these slices of state? Can you guarantee that the type definitions within a given reducer or selector will remain well defined and clear to read?
These are some hairy questions that always do have answers, but the answers frequently stray into the realm of “worse than the problems they were meant to solve”.
Not all apps can guarantee certain behaviors will always happen, or that they will happen the same way. For example, my current work projects are configurable, multi-tenant applications. This means we sometimes change which components are rendered or how they behave based on runtime conditions and feature flags (from a configuration object, plus the currently logged-in user’s particular data). The result is that it becomes difficult, at best, to keep in mind all possible interactions that will be available to the user, which data to retrieve and show, or which format that data will take when rendered. This gets even harder as the application evolves, and more “sometimes there, sometimes not” features are added to each page. This is compounded by the fact that a lot of these concerns that may have started out as global have now become specific to conditions in the UI itself (i.e. which components are rendered where and with what options), which pulls state back to our components (see condition #2).
The short version of this is, if there is a lot of flexibility in your app, it sometimes is best to choose the right component to render, and just let that component handle things for itself and it’s own children.
Conclusion
If your app meets all three conditions, I bet you will find NgRx (or Redux) to be a valuable addition to your frontend project. If it only meets one or two, I would personally be pretty reluctant, but there are exceptions to every rule (leave a comment with what exceptions you have experienced!)
One example of an exception I have seen is to the third point; large, configurable/dynamic forms. It might seem like being so dynamic might mean too much difficulty in defining state, but form controls virtually always have an exceptionally well defined interface. This sometimes creates situations where you can easily model state without knowing a single form control’s name/property ahead of time, as long as you know that it will always be a form control.
Top comments (0)