State managers for Angular

Table of contents


1. Background
 

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.
 

3. Conclusion

Background

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.

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.

2. Through Angular services by using simple variables for temporary data saving and Promises as the most common type of Push system in JavaScript today.

Secondly, there are two more options, which are more complex, but at the same time more powerful and efficient:

3. Through Observable data services — Angular services with RxJS library, which is the reactive “b̶e̶a̶s̶t̶” extension of JavaScript.

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

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

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.

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.

State management technique in Angular — RxJS Behavior Subjects

Behavior subjects are similar to regular subjects in RxJS, except they hold onto the “current value”, allowing observers to view the value even if it was last updated prior to subscription. Like subjects, they allow “multicasting”, or simultaneously updating all listeners. What this means is you can keep your behavior subject in a place accessible by multiple components, such as a service, then subscribe to it in multiple components and have all of them receive updates as they occur, regardless of the position of those components in the hierarchy — which is super neat!

simple counter app to show how behavior subjects work, you can view the source code on Github by clicking here.

The example app is a simple counter with buttons to increment, decrement, and reset the count back to 0. There is a reusable component that is listening to the behavior subject — twice! Both instances of the component will update simultaneously anytime there is a change in the current value stored in the behavior subject. Let’s look at how it works.

In the service, this is how the behavior subject is created. I am using an interface called Count to enforce the object structure acceptable for the behavior subject.

initalCountis what the initial value of the count should be (this is also used when resetting). Line 11 is where a new behavior subject is instantiated with 0 as its first value.

There are three methods I made for interacting with the behavior subject. First there is getCount(), which enables a component to receive and subscribe (listen) to the behavior subject. Next, setCount() allows a new current value to be provided to the behavior subject, overwriting the older value in the process. It requires the current value and the delta, or change in value, so that the new value can be computed.

Finally, resetCount() reuses that initialCount variable that resets the count back to {value: 0} again.

Lets see these methods in action.

Above is the component with the buttons that control updating the behavior subject. The increment, decrement, and reset functions are called on click on their corresponding buttons.

When the component is initialized in ngOnInit, it subscribes the the behavior subject so it has access to the current value, which is necessary for changing it.

Now lets look at the observer components.

Here is the component displaying the current behavior subject value.

This component is instantiated twice, to have two unique, simultaneous listeners. They don’t modify the behavior subject like the previous component, but only passively subscribe to receive updates. In this simple example it’s not particularly impactful — but imagine this was a reusable component that was embedded in very different parts of the app hierarchy but always needed this information.

This makes it far simpler to provide the component with the data it requires, instead of passing data down from parent components!

If you console.log the behavior subject, you can actually see something really cool — check out the observers property — we see 3! One for the first component that modifies the behavior subject, and two more for the components only listening. The behavior subject itself keeps track of this, which is both useful and fascinating. The current value can also be seen, as well as data pertaining to the state of the observable.

Most of the applications will work well with this approach. But for bigger applications, once you add multiple shared services for different components, the code soon becomes cumbersome to manage. Due to this, the debugging of the application may become difficult. Most of the time, few components will deal only with the subset of the complete state which makes it challenging to determine the state throughout the app. Also, adding to this, if there are multiple actors such as WebSockets, some user interaction and a few workers that are manipulating the state simultaneously, it can carry a major possibility of contriving the Race Condition. This can also hamper the predictable behavior of the application.

Redux Pattern with RxJS

Redux, referring to the docs, is “a predictable state container for JavaScript apps”. Redux concept is astonishingly simple and elegant because data moves in one direction only, data flow is explicit and predictable as it’s always coming from one source.
 

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.

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.

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.

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.

This is the representation of example state, the customer list.

The code for this app is available here.

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!

Made with Slides.com