NgRx app refactoring - some boilerplate-reduction ideas with examples
In the Angular application I’ve been working on for the past few months, I found that I typically need to manage three things when dealing with any part of the application’s state that is associated with fetching data from backend. These are: resource data itself, whether that resource is currently being loaded, and if there are any errors. I would like to outline how one may start with designing a simple application dealing with this, and then proceed on to how one may come up with different ways to simplify, improve, or otherwise approach it differently, especially how one may attempt to reduce the ‘boilerplate’ associated with NgRx or other Redux-based applications. By ‘boilerplate’ I mean defining multiple similar member variables, classes and selector functions to manage similar things in the same way over and over again. Companion application for this article is available here — there are 6 commits representing different things being changed at a given stage of refactoring, plus one for Angular CLI’s initial commit.
Stage 1: initial application functionality
Code for the application at this initial stage is available here.
At the beginning we have a simple user-listing application with two views: user list and user details. Progress spinner is displayed on each until the actual data gets loaded. It’s a NgRx application, with is a single slice of state (a ‘domain’, if you will): users. In users state we manage 4 properties:
- users, which is a list of user objects,
- userDetails, which represents the user object we currently want to see the details of,
- loading, which is a boolean for whether a server request is in progress or not,
- error, which represents an error we could potentially receive from the server in case anything goes wrong — state property for error is there only for completeness’s sake, we don’t manage errors in this demo application.
There is no backend, it’s just simulated by creating delayed observables returning some static data — 2 seconds for users list and 4 seconds for user details.
To manage the fetching of all users for the list view there are 3 action types — named differently in code as you will see below, but for illustration we will refer to them from now on using the following descriptions:
- perform - dispatched manually in container components for simplicity — sets loading state to true, data state to false and error state to false
- success - emitted in UsersEffects class in case of successful response — sets loading state to false and sets data state to acition’s payload
- failure - emitted in UsersEffects class in case of failed response — sets loading state to false and error state to action’s payload
There are three analogous action types for management of user details; all action types are listed here:
A this stage, the component displaying list of users and the component displaying user details both utilize the same loading and error state properties, accessed in components as observables of these state properties.
This is fine for now, as we don’t display both the list and single user details simultaneously. But, as you will see, it will turn out to be a problem in the next stage.
Stage 2: introducing dashboard
Code available here.
At this stage we introduce a dashboard view. A single user (always the one with id: 1, for simplicity) and the list of all users are displayed simultaneously on this view. Fire up the application at this stage and navigate to dashboard by clicking a button from the initial list view. The actions influencing both userDetails and users state properties manage the same loading property, and an observable of its current value is passed to both app-user-details and app-users-list components:
Once the request for user list finishes (2 seconds), the progress indicator disappears and both the list and empty user details are displayed. However, the request for user details isn’t finished yet (4 seconds), so the user details are empty, as illustrated below:

The same is true for error state we don’t manage — but if we did, and we displayed the received error in each component, they would share the error state, as they’re now sharing the loading state. So we need to have separate error and loading management for users and user details.
Stage 3: split of loading and error state properties
Code here.
So to immediately fix the problem, the first solution that comes to mind is to split the error and loading state properties into two distinct ones for users and user details, and that’s what we do at this stage.
app-user-details component subscribes to observable of userDetailsLoading state property, and app-users-list component subscribes to observable of usersLoading state property. Analogously we have two separate properties for error state, and corresponding two observables for these:
Now the user details panel is displayed properly — spinner disappears after 4 seconds, once the user details actually gets loaded.
Even though the problem seems resolved, what if we have much more to display on the dashboard in the future — say we need to: add a category to a user, display premium users separately, promote one to premium/paid user, remove a user, send an email, add a role… If the requirements are to handle all these concerns with a separate backend endpoint and to manage all that in our single dashboard view, and they dictate to display separate loading indicator and error message for various components dealing with these various parts of state, and we continue down this road, soon there will be n*3 state properties — for data, loading and error of each aspect of the application’s state. These will each need a selector function in our reducer file, will need to be represented as a variable in container components and passed as Inputs to any consuming presentational components. Also, there will be three standard actions for each data-holding property — previously mentioned ‘perform’, ‘success’ and ‘failure’ actions — and currently we have a distinct class to represent each action. This quick code multiplication is common in redux-based applications, let’s see if we can do something about it.
Stage 4: moving loading to ngrx effects class
Code here.
usersLoading$ and userDetailsLoding$ are both observables of a boolean state that’s being toggled in response to reducer’s action handling process. At this stage we move these loading-representing boolean variables from our state model and our reducer and attempt to represent them as observables in our Effects class. Such observables would emit true once an action of type ‘perform’ is fired, and would turn to false when either ‘success’ or ‘failure’ actions are emitted. This is implemented at this stage — a convenience function to create loading observable is implemented:
and is used in UsersEffects class to create userDetailsLoading$ and usersLoading$ observables (only usersLoading$ demonstrated below —the same applies for userDetailsLoading$):
These are then consumed by the container components. In the @Effect() decorator we specify dispatch:false, as createLoadingObservable returns an observable of boolean and not of any Action type, and we don’t want a boolean to be dispatched on our store (which would result in invalid action error). Simultaneously, we remove usersLoading and userDetailsLoading properties from state model, from reducer logic, and we remove the selector functions for these state properties.
Now the loading concerns have been reduced to mere side effects, and although in terms of code reduction there isn’t much gain, this way of managing some part of state (i.e. as an observable from Effects class instead of a direct observable of state) is something you may consider in your project. Quick tip: we could follow with an analogous implementation for errors.
Stage 5: refactoring actions
Code here.
At this stage, the actions file has around 60 lines of code (admittedly — with pretty loose spacing), with code for just 6 actions and their type alias, although we represent logic for basically two things: getting all users and getting user details. If we keep the spacing as it is now, and add actions for multiple things mentioned previously as potentially planned for dashboard, the actions file will quickly come close to being 200 lines long.
When I tried to minimize the process of repetitively coding three classes for each new operation, while maintaining type safety, I came up with a solution, the aspects of which are outlined here:
- agree upon a convention that each ‘success’ and ‘failure’ action’s type property will consist of the ‘peform’ action’s type property with appropriate suffix: _SUCCESS for ‘success’ actions and _FAILURE for ‘failure’ actions
- instead of action classes, represent them as action-creating static functions, placed on the Actions class
- represent each type of action — ‘success’, ‘success’ and ‘failure’ action as such static function
- define type information for payload of each action object that each such static function should return
- create a typescript decorator for ‘perform’ actions, which would define logic for creating action objects for all three types of actions: ‘perform’, ‘success’ and ‘failure’, and annotate the ‘perform’ actions with it:
Code for the decorator function:
What’s lost using this solution is the union type (Actions) that we used in reducer function signature to provide type information for action parameter — to specify that only certain action classes are allowed to be handled by this reducer function (illustrated below, the abovementioned union type accessed here as userActions.Actions):
But when one thinks about it, the union type only assures that when we access action object in reducer function’s body, we access only the properties of the union type — i.e. the properties present on each action class that our union type consists of. That leaves in our case only type and payload properties. Since type and payload properties are the only ones accessed in reducers 99.9% of time, I’d be willing to sacrifice that, and define the type that will be used everywhere throughout the application for action objects — UserAction — and use this instead of the union type that’s no longer there. This type declares that a valid action must have type and may have an optional payload property, which covers vast majority of actions I ever used in my projects.
The actions file is now much less cluttered and is below 30 lines of code:
Stage 6: refactoring actions
Code here.
I had to move things around a bit so the AOT didn’t complain about using function calls in state initialization, so I moved the logic for initial state creation to a function that is a factory for INITIAL_STATE provider. Support for providing a function for initial state value landed in ngrx/store in version 4.0.5., which was not yet available on npm at the time of this writing.
At this next stage we make it so that all three ‘state concerns’, if you will (data, loading, error), of a given state property (userDetails for example) are represented in a single object. That is — userDetails will no longer hold a simple User object — it will be wrapped with a StateItem object that contains information about the property’s data, loading and error. The vision is that all future extensions of state model in this application would also follow this methodology.
StateItem class has members to represent error, loading and actual data, it has methods to set and get each of these, as well as methods for controlling what state it’s in — is there an error, is it loading or is there actual data available.
Looking at the code we see that from now on we can store just a single property in our state model for each aspect of the application (just under userDetails for example) instead of two (separate userDetails and userDetailsError), or even three as we had at stage #2 (add userDetailLoading to these). Below you can see the new interface for state related to users:
As a result, we remove the properties representing loading and error from state model and reducer, as well as their selector functions. Also, components have fewer Inputs, and we no longer need the member properties representing loading in Effect class (look at the diff). We can therefore in addition remove the createLoadingObservable utility as well as usersLoading$ and userDetailsLoading$ observables from the Effects class.
Summary
That’s it — at the beginning we had a simple appliation that illustrates a typical starting point for an application as you begin to implement it and add some basic functionality. The process of adding functionality typically consists of repeating the same steps, as you add new capabilities based on data you fetch over the wire:
- add a state property for data, for error and for loading of a given part of state, be it users collection, user details etc.,
- define three action types (to signal data fetching, successful response, and errors),
- define side effects and dispatch actions in response to them appropriately,
- implement selector functions to get only the specific slices of state out of the overall state observable, which you can then in turn use in your container components to obtain observables of that particular state,
- define members in container components to hold these observables,
- pass these observables from container components to be consumed by presentational components via Inputs.
We then moved through several ideas on what steps one may take in an attempt to reduce the amount of ‘code repetitiveness’ if you will, associated with this process, which is seemingly inherent in NgRx- or generally Redux-based apps. I hope you enjoyed them and consider experimenting with them in your own projects, or perhaps you got a tiny bit inspired and have already come up with something better. If so, or if you have additional techniques of your own you use daily, have some objections related to these ideas, or you liked the article, feel free to write a comment below:)
If you enjoyed this article, consider recommending it on Medium (clap/heart it), and sharing it on Twitter, LinkedIn, etc.
