Table of contents
2. State management strategies: what are the options?
- Through component’s interaction via input bindings and output event emitters;
- Through Angular services by using simple variables and Promises;
- Through Observable Data Services — Angular services and RxJS;
- Through Redux Pattern and RxJS.
If you are new to front-end architecture and are creating a plan or already getting ready to start a new single page application, you are at the right time and place. I’m here to outline mistakes what I did and to prevent you from reinventing the wheel around your future app’s structure and huge data management by providing the right and clear solution.
No matter which framework you choose — you should ask yourself about the app’s state management first because building a frontend architecture and choosing the appropriate way of managing state is one of the biggest challenges.
Even if your application seems to be not complex for now but you should know that doing lots of simple things requires a lot of data manipulation, which truly means the data mutations and the appearance of side-effects. This obviously leads to tons of time-taking, fatigue bugs, and poor application performance. Thus, in order to relieve the pains of such problems without getting messy pretty fast, you should figure out how to maintain local UI state of the application.
As I’m an Angular developer, I would like to describe below the options of handling a state management in the scope of Angular, provide you essential tips and help you to understand what is the best option for building fast and powerful web applications.
State management strategies: what are the options?
First of all, as Angular is a comprehensive JS framework/platform that contains everything you need to solve all kinds of development challenges — it provides us with two built-in state functionality, specifically:
1. Through hierarchical component’s interaction, typically use of stateful and stateless components via @Input bindings and @Output custom events.
Secondly, there are two more options, which are more complex, but at the same time more powerful and efficient:
4. Through Redux Pattern with RxJS library.
Let’s dive deeply each one by one.
Hierarchical component ’s interaction
The main concept is having a “stateful” parent component that delegates down into a “stateless” children components. Such a structure has some significant features: it’s explicit and predictable, it’s simple to test, and it’s not difficult to see what’s impacted when a change is made. When data changes in a parent component, it’s easy to find the downstream child components that could be affected (see Picture 1).
However, such approach is handy only within a very simple application and as soon as your application’s architecture becomes more complex or you just need to share data between separate modules/components through Angular services, it becomes useless and painful (see Picture 2).
Angular services, variables & Promises
As immutability is a core principle in functional programming, then this approach also has the right to life only within a very simple application. Why?
First and the main problem within using simple variables for temporary data saving is that you cannot concurrently and over time track data changes in multiple separate components through Angular services in an appropriate way. Moreover, there’s a huge chance of mutation the same data instance by reference in one of the components, which leads to unpredictable issues and consequences through the entire application. Thus, it won’t take a long time for you to understand that shared mutable state became a disaster.
Second, but a not less important weakness of the current approach is Promises. As Angular natively provides support of Observables, which offers significant benefits in handling multiple values over time, the usage of Promises with its single values obviously seems a huge step back!
Have a look on main Promises disadvantages:
- you cannot run Promise only when you need it, because it executes immediately and just once — on the creation;
- Promises return only a single value or an error message;
- the request initiated from a Promise is not cancellable, e.g. an HTTP request that does a search on the key-up event would be executed as many times as we press the key;
- in order to have a retry capability of a failed call — you might get a callback hell.
Therefore, Promises are definitely hard to manage in large applications, moreover, you are losing a vast functionality in comparison to the Observable pattern.
In conclusion, we have already considered two built-in approaches of the state management implementation in an Angular application, which are possible to use without a headache only when your app consists of a few components. And in order to handle the state management of an application that has an army of components all working together, you should consider the usage of another significant and almost built-in Angular toolbox — RxJS.
Observable Data Services — Angular services with RxJS
Vast asynchronous experience is a part of daily web applications, and RxJS is intended to solve all problems relative to Promises and data changing over time, in order to bring a user experience to the new reactive level.
Observables data services — data streams that provide more flexibility in developing the application and managing the application’s state by using multiple Angular services (singletons).
In my opinion, the Observable store pattern is a great solution for simple applications instead of cumbersome third-party library store. However, RxJS doesn’t solve any problem and it definitely has concerns. The main concern is that there is no any centralized, I mean unified and approved system how in a proper way to implement the custom state management for an application.
Let’s consider an example from my past experience(written on Angular 5 and RxJS 5.5.11 version) — it’s a simple application which has three main routes:
1. Customers — user can see the list of customers and its details.
2. Products — user can see the list of products and its details.
3. Invoices — user can see the list on invoices and its details, also, a user has the ability to add a new invoice, view, edit and delete a specific invoice.
This approach is based just on pure RxJS streams. Let’s consider it in details. Firstly, we simply fetching data from API in invoicesList$ stream, then pass its value (a collection of invoices) to invoicesListCombined$ stream, which also includes customersList$ stream (a collection of customers), declared in customers service. Current stream simply transforms each invoice by adding customer info to it.
Afterward, in order to implement functionality within invoices according to the specs and to have an ability to expand it in the future I’ve created the invoicesCollection$ base stream, which has an async subscription in a template for displaying a whole list of invoices to the user. Then, obviously, I’ve expanded it with 2 another streams: addInvoiceToCollection$ and deleteInvoiceFromCollection$, which just respectively transform the data of the main invoicesCollection$ stream.
All in all, it might look pretty simple, but, believe me, it’s not. First of all, you should know well the RxJS operators. Secondly, you should define which streams supposed to be “hot”, “cold” or “warm” and would it be re-used in other services/components or not. Thirdly, it’s very easy to mess up and afterward spend lots of time on tedious debugging. Fourth and the main important, it’s scalable only within this current service and it’s not reusable at all, because you will have to repeat plus modify all this code in any other service in order to work with another data.
However, though this approach works out fast like a hurricane, I’ve decided to find out another more efficient solution, which is scalable and reusable for the whole application.
It’s taken me some time to find scalable and reusable custom solution, that eventually, turned out to be not worth that, but first things first. In order to follow the DRY principle and make our State implementation reusable through the whole Angular application, the first thing that came to mind was to create a Class with a generic type (e.g. class StateManagement). Then, I’ve added a simple collection$ stream to this Class, which would represent each of our future data collections. So, every time by calling StateManagement Class through the “new” operator — I would receive a new instance of that Class with new data inside of collection$ stream.
Afterward, as I’ve needed the ability to transform data in the future, obviously, I have added to the Class basic CRUD functions of persistent storage. Thus, all code responsible for the managing of an application State has been declared only in one place and that was a significant improvement in comparison to my previous example.
Moreover, I would like to outline two main issues which flared up within this approach later, specifically:
Combining different separate States — it won’t take me a long time to mess up with lots of “CombineLatest” through the entire application, what nothing less than completely reduces readability and adds complexity to the code. What about other developers, who might spend plenty of time to get to the bottom?
Sequential requests and catching/processing errors — as an example, I’ve needed to receive the State of Users first, then based on a current User Id receive a State for specific Invoices. As the “StateManagement” Class is unified it becomes an issue to find a proper place for that specific request to be done and catch/process its errors. What if my application would scale up with new features and I would need to add more separate sequential requests? Yeah, exactly, the code consistency will be totally lost.
In conclusion, if you want to implement a powerful solution for a state management and leverage the built-in features of the Angular framework — the Observable Data Services approach would definitely work out!
However, I would like to point out that:
1. You must be well versed within RxJS library and Observable Pattern itself. You should know what is the “cold”, “warm” and “hot” Observable, what’s the difference between it, how to transform one to another, which and when to use.
2. It’s pretty hard to catch and process errors, moreover it’s extremely difficult to test an application if it’s behaving according to specs.
3. It’s definitely time-taking to understand, maintain and in large applications for other developers because each solution is absolutely custom.
In order to solve the listed above issues within custom State management approach and be aware of unnecessary work, I’ve decided to find some another solution and the eye-catching one was REDUX.
Redux Pattern with RxJS
Before we start with an example, please consider some definitions:
- Component — view template user can interact with;
- Action — defines (dispatches) the change in State that is to be made;
- Reducer — a pure function, meaning, it doesn’t produce side effects , which has access to the current State;
- Selector — defines which specific data get from the Store;
- Effect — handles everything that is asynchronous or outside the application.
At first glance, it might seem pretty complicated and time-taking because you would have to set up the necessary application’s structure and write lots of boilerplate code, but let’s find out how it works and what actual significant benefits it brings.
Let’s consider a simple example for describing the Redux data flow in details. Let’s imagine that user clicks a button in a component’s view template, then the corresponding Action is fired (dispatched) to the Store. When the Action is triggered, the Reducer takes the current State and the data from the Action then returns the new State from it.
Reducers don’t store or mutate State — they are just taking the previous State and an Action, and return the new State.
Thus, the first benefit by maintaining all your State in a Store and using the async pipe to wire up to the view is an ability to easily control the change detection that significantly boosts the performance in enterprise applications.
Also, in Redux we have Selectors, which are called with the Store’s select method. Have a look at Picture 4.
The Store’s select method knows how to get the current State via Selector and returns stream, which emits values whenever State changes that allow the component to be informed and receive the latest version of the data from the Store. In this way, we can make no doubt that data flow is explicit and predictable because it’s always coming from one source.
The second benefit is that selectors solve one of the main listed above problems of the Observable Data Services approach (custom state management) — combining of different separate states. A Selector also can pass and group together different slices of the State in order to create the data that a specific component needs.
Moreover, we have effects, which also give us a third benefit and solves the issue within sequential requests that custom state management approach doesn’t in a proper way. An Effect listens to an Action and after processing on the side dispatches one or more Actions, which are also listened by another Effect(s) that fires up another Action(s), which are then again and again processed by the reducers. Such chaining might be as long as you need. Please, have a look at Picture 5.
Besides, as a well-implemented system is only half of the work, another half are automated tests and debugging.
The fourth but a not less important benefit of Redux methodology is that we are separating the business logic from rendering, that allows testing two parts independently.
Testing our logic translates into testing of Actions, Selectors and, of course, Reducers, which are pure functions itself and allows us to test complex UIs by asserting that functions return specific data. In addition, you can find a great npm or browser extension for debugging process — Redux DevTools, which might save you plenty of time. It lets you inspect all your workflow — you can observe every State and Action payload, moreover, if the Reducer throws an error, you can see during which exact Action that happened. Isn’t amazing?
All in all, I would like to outline that Redux methodology definitely has benefits and worth to spend some more time versus custom state management approach, basically because the last is really time-taking to implement, scale, maintain, test and debug.
In this article, I’ve tried to shed some light on the best option for managing the state of an application. Unfortunately, there’s no the “best” or universal approach. Which methodology to use really depends on your application, your use case, and your organization’s needs and constraints.
Looking back on all listed above approaches functionality, it’s advantages and disadvantages I would like to highlight Redux methodology. Such functionality and one-way data flow allow you to understand what is going on in your application in a more predictable way. It is scalable, reusable, and you clearly understand where the data is stored and how it’s shared among the army of components. However, you should only implement Redux if you determine your project needs a state management tool, otherwise, you are encouraged to use listed above Redux alternatives, but please, don’t shoot yourself in the foot — consider my above experience and don’t try to reinvent the wheel!
Please clap if you enjoyed this article and visit our site 2muchcoffee.com