Managing State in Angular 2 Apps with ngrx/store
[special]The components we build for our web applications often contain state. Connecting components can lead to sharing mutable state: this is difficult to manage and leads to inconsistency. What if we have one place where we mutate the state and let messages do the rest? ngrx/store is an implementation of Redux for Angular, using RxJS, that brings this powerful pattern into the Angular world.[/special]
In this article, I will introduce the problem of shared mutable state and show how you can solve this using the ngrx/store library to bring the one-way data flow architecture to your Angular 2 apps. Along the way, we’ll build an example app that allows a user to search for videos using the YouTube API.
Note: You can find the code that accompanies this article in this GitHub repo.
The Problem With Concurrency
Building components which communicate with each other is a typical task involving state. We frequently have to keep up to date with different Angular components interacting with the same state: when more than one component accesses and modifies that state we call it shared mutable state.
To understand why shared mutable state represents a problem, think about a computer which is being used by two different users. One day, the first user updates the operating system to the latest version. The second user turns on the computer one day later and is puzzled since the user interface has changed for no apparent reason. This happened because two users could modify the same object (the computer in this case) without speaking to each other.
Shared Mutable State in Practice
A common example of shared state is the set of properties of an action we are performing. If we are performing a database search, we call that set of feature the current search. From now on, I’ll refer to such a set as the search object.
Imagine a page which allows you to search for something by name, and also offers the possibility to restrict the search by geographical location. This page will have at least two different components that can modify the current search properties. Most likely, there would be a service responsible for performing the actual search.
The rules would be:
- if the name field is empty, clear the search results
- if only a name is defined, perform the search by name
- if both name and location are defined, perform the search by name and location
- in order to search by location, both coordinates (lat/long) and a radius must be provided
The Available Approaches
A way to deal with the problem of shared mutable state could be to forward the search object back and forth between components and the service, allowing each to modify it.
This would entail more verbose and complex testing, which is very time-consuming and prone to error: for each test you would need to mock the object, changing just some properties in order to test just specific behaviors. All these tests and mocks also need to be maintained.
Also, every component interacting with the state will need to host the logic to do that. This compromises reusability of components and violates the DRY principle.
An alternative approach would be to encapsulate the search object into the service and expose a basic API to modify the search values. Nonetheless, the service would be in charge of three different things:
- performing the search
- keeping the state consistent
- applying the parameter rules
Quite far from the Single Responsibility Principle, the service has now become the application itself and can’t easily be reused.
Even splitting up that service into smaller services will still result in a situation where we have different services or components modifying the same data.
Furthermore, the components are consuming the service, thus they cannot be used without the service.
A different and often-used pattern is to put all the logic into the application layer, but we’d still end up with a great amount of code in charge of guaranteeing the state consistency.
My opinion is that the application layer, which is the real distinctive trait, should only apply the rules. Other tasks, namely message passing, storage, and events, can be handled by the infrastructure.
The Redux Approach
This pattern is also available to AngularJS developers in several implementations. In this tutorial, we will use ngrx/store since it is part of the
ngrx package which is the official Angular 2 wrapper for the Reactive Extensions. Furthermore, it implements the Redux pattern with Observables, thus staying consistent with the Angular 2 architecture.
How does it work?
- components emit actions
- actions are dispatched to a state store
- reducer functions derive the new state based on those actions
- subscribers are notified of the new state
So, we can share responsibilities since ngrx/store is taking care of state consistency while RxJS brings the messages bus.
- Our components will not know about services or application logic: they just emit actions.
- Our service has no state: it just performs a search based on a search object coming from outside.
- Our application component just listens to state changes and decides what to do.
- The new entry, the reducer, will actually react to actions, modifying the state if necessary.
- One entry point for mutations.
Example: A YouTube Search Component
We will write a small application to search for videos using the YouTube API. You can see the final demo running below:
Continue reading %Managing State in Angular 2 Apps with ngrx/store%