In this post, we are going to walk through how Angular applications can be built in a Functional Reactive style using the RxJs library that is part of Angular (see Managing State in Angular Applications by Victor Savkin (@victorsavkin). We will go over the following topics:
- The difficulty of handling state in single page applications
- When is a Flux-like architecture needed?
- Building a Flux-like Angular Application using RxJs
- Application Actions
- How to build an Action Dispatcher in RxJs
- Building an Application State Observable
- Consuming Observables using the async pipe
- Where to use RxJs: Smart vs Pure Components
- Comparison with Redux
- Conclusions
This post is better read after going through Functional Reactive Programming for Angular Developers - RxJs and Observables, where several of the RxJs operators used in this post are presented.
The most important question about this type of architecture is probably when should we use it and why? Have a look at this other blog post that goes over that topic - Angular Service Layers: Redux, RxJs and Ngrx Store - When to Use a Store And Why ?
If you are getting started with Observables and Angular, you might want to have a look at this post where we go over some common trouble scenarios.
If you are using the centralized store pattern and Ngrx Store you might want to have a look at this post - Angular Ngrx Crash Course Part 1: Ngrx Store - Learn It By Understanding The Original Facebook Counter Bug.
The difficulty of handling state in single page applications
Probably not all single page applications have a large state management problem. Imagine a simple CRUD app used to administer reference data for security privileges.
For that type of single page application probably using Angular Forms and/or NgModel
if you so prefer is an approach that will yield good results with low complexity involved.
But there are use cases where this approach is known to fall short. The most common example is the famous unread messages counter issue in Facebook, that lead to the creation of the Flux architecture (see here the original talk).
When is a Flux-like architecture needed?
As one of the core members of React (Pete Hunt - @floydophone) says in the React How-To, you probably don't need Flux! When you need it, you will know when you need it. If you have a use case like the following, you probably need Flux:
- multiple parts of your app display differently the same data. For example, an outlook app displays a message on a list and updates folder counters of unread messages
- some data can be simultaneously edited by both the user using the UI or events coming from the backend via server push
- you have a need for undo/redo of at least part of the application state
These use cases are actually not that infrequent, depending on the type of app.
An architecture for complex UIs
Think of the Netflix UI, where a movie potentially appears in multiple lists. If you add a movie to your favorites and scroll down and see that movie again in another list, you want to see it marked as a favorite already. Again, the same data shows up in multiple parts of the UI and an action needs to affect the whole UI consistently - this is a typical use case for Flux.
Is Flux/Redux the only solution for use cases like this?
There are other options too besides the single atom of state option presented in the rest of this post.You can for example like building the application around the notion of observable data services. That's probably just another version of Flux.
But let's assume you stumbled upon one of these cases in a part of your app: How can we build a Redux-like single atom of state application in Angular?
Building a Flux Angular app using RxJs
As in other Flux approaches, it all starts with the UI Actions. The user interacts with the page and as a result, multiple parts of the app want to respond to that interaction.
Application Actions
An action is a message that defines what happened in the UI: A Todo was added, removed, toggled. To make this type safe lets create a class for each action:
And then lets define a Typescript union type that is a union of all the action types defined:
As we can see, the Actions are just POJOs that transport the data necessary for the different parts of the application to adapt themselves.
But how can an Action be dispatched to multiple parts of the application that need it?
Using the Action Dispatcher
An action is dispatched via the action dispatcher. This gets injected in any place of the application that needs to dispatch actions, typically Smart or Controller-like components like the TodoList in the sample application:
You might be wondering what the dispatcher
name inside the @Inject
annotation is, this is just a token name for identifying a specific injectable, more on this later. The important thing to know now is that the dispatcher can be used to dispatch any action to the rest of the application:
As we will see later, the dispatcher was built in a few lines of RxJs. But before seeing how it's built internally, let's see how other parts of the application can react to an action.
Defining the application state
Let's start by defining what the application state looks like:
As we can see, the application state consists of a list of todo items which is the data of the application, plus an instance of UiState
. Let's take a look at UiState
:
UiState
contains any state in the UI other than the data, such as for example what is the message currently being displayed to the user, and a flag indicating if some action is ongoing in the backend.
Introducing the Application State Observable
Now that we know what the state of the application looks like, we need to imagine a stream which consists of the different states that the application has over time: the application state observable.
As new actions are triggered, this stream will emit new values that reflect the result of those actions: todos get added, toggled, deleted, etc. What we are looking into is this:
We will see later how we can create such an observable, it will only take a couple of lines of RxJs. For now, let's assume that the application state observable already exists and that it can be injected anywhere in the application:
This means that any part of the application that wants to react to new state can have an observable injected and subscribe to it. That part of the application does not know what action triggered the arrival of the new state: it just knows that new state has arrived and that the view should be updated to reflect it.
How to use the application state observable
We can subscribe to the application state observable like to any other observable. Let's say that we want to take the list of todos and pass it to the template. We could subscribe to the observable and populate a todos list member variable:
This would be one to do it, but Angular foresees a better way.
Consuming observables using the async pipe
Another way to consume observables is to use the Angular async
pipe. This pipe will subscribe to the observable and return its last result:
Here we can see that the list of todos comes from a todos
observable. This observable is defined via a getter method in a controller class and is derived from the application state observable using the map
operator:
As we can see it's simple to build a Flux app once the dispatcher and the application state observable are available:
- inject the dispatcher anywhere an action needs to be triggered
- inject the application state anywhere in the application that needs to react to a new application state
Let's now see how these two constructs can be built using RxJs.
Building an Action Dispatcher
The dispatcher is really just a traditional event bus: we want to use it to trigger events, and want some part of the application to be able to subscribe to the actions that it emits.
To implement this, the simplest way is to use an RxJs Subject, that implements both the Observable and the Observer interfaces. This means we can not only subscribe to a Subject, but use it to emit values as well.
Making the dispatcher injectable
The dispatcher to be injected anywhere on the application needs an injection name. Let's start by creating such name using an InjectionToken
:
This token can then be used to register a Subject in the Angular dependency injection system:
This means that whenever the dependency injection system gets asked for something named dispatcher
, this Subject will get injected.
Avoiding event soup while using the dispatcher
Although the dispatcher is a Subject, it's better to mark its type upon injection as an Observer only:
This is because we really only want the dispatcher to be used in application code to dispatch events, such as for example:
We want to avoid most application code to accidentally subscribe directly to the dispatcher, instead of subscribing to the application state.
There might be valid use cases to subscribe directly to the dispatcher, but most of the time this is likely not intended. With the dispatcher defined, let's see how can we define the application state observable.
Building an application state observable using RxJs
First let's define an initial state for the application, again using an injection name of initialState
:
This means that whenever the name initialState
is requested for injection, the object defined above is passed in: it contains an empty list of todos and some initial non-data UI state.
Defining the application state
The application state observable can be built as follows:
The application state is a function of both the initial state and the stream of actions that occur over time:
- the first value of the state observable is the initial state itself
- then the first action occurs and the initial state is transformed accordingly and the state observable emits the new state
- the next action is triggered, a new state is emitted
Calculating the new application state
Each new state is the result of applying the new action to the previous state, which looks a lot like a functional reduce
operation. The first step to calculate the new application state is then to define a series of reducing functions, Redux style. Here we have the reducer function for all todo-related actions:
This is a typical reducing function: taking a state and an action, calculate the next state of the todo list. A similar reducing function
calculateUiState
can be defined for the UiState
part of the application state (see here).
Using reducers to produce a new application state stream
Now that we have the reducer functions, we need to define a new observable stream that takes the actions stream and the initial state, and produces a new state.
In a previous post, we went over some commonly used RxJs operators. It's time to use the first of those operators, the scan
operator. This operator takes a stream and creates a new stream that provides the output of a reduce function over time.
What we are doing here is taking the current state of the application, starting with the initial state, and then continuously calculate the new state as a result of the previous state and the action itself.
The output of scan
is also an observable whose values are the several states of the application over time.
And that's it! As the Redux docs themselves mention, an alternative to Redux is a few lines of RxJs! But there are still a couple of loose ends we will look into.
Avoiding reducer functions from being called multiple times for one action
While debugging an application built like this, you will notice that if you add multiple application state consumers (like multiple async
pipes), the reducer functions will be hit multiple times, one for each subscriber.
This is because each observable by default spawns a separate processing chain, see this previous post for further details.
Although there is nothing fundamentally wrong with that, for the sake of simplicity of debugging you probably want to ensure that the reducer functions are only triggered once per action dispatched, like in Redux.
For this we introduce the second RxJs operator we will use, the share operator (see here for more on that):
There is another loose end, which is how to use the application state observable in an application startup scenario.
Ensuring that the application state observable can be consumed at application startup
You might want to inject the application state observable and use it in places in your application where your whole app is not completely setup yet.
For example, at application startup time, like here. This will not work the way you expect, because it's possible that not all subscribers are wired at the moment the first application state value returns.
The solution for this is to make the application state observable a stream that when subscribed always returns the last value, even if it was emitted in the past before the subscription took place.
For this, we need to wrap the plain state observable into a
BehaviourSubject
:
This will ensure that subscribers will always receive at least the initial state value.
All Together Now
This is a working example of how to build an application state observable (see here as well):
We can use this factory function to create an injectable application state observable in the following way:
And this wraps up how an application state observable can be built. Let's remember that once this initial plumbing is in place, it's really a matter of injecting the dispatcher and application state where we need it, and mostly writing new reducer functions.
Where to use State and Dispatcher - Smart vs Pure Components
In general, the dispatcher and state observable should only be injected in smart components. Those components don't need to have any state variables, as they can consume the state observable directly using the
async
pipe.
It's not that the application has no state, but that the application code has no state. The state is managed by the RxJs library and not at the level of the application.
How should pure components use dispatcher and state
Pure components can receive observables as input streams, but they should not have the dispatcher or state observable injected into them, as this would bind them to this particular application, making them not reusable.
If a pure component wants to dispatch an action, it instead issues an event via EventEmitter
, and it's the smart component that will subscribe to the event emitter it and in response dispatch an action.
Comparison of building Angular apps with Redux
Let's keep in mind that we will already need to know RxJs to use Angular, because Observables are part of the Angular API for things like Forms or Http.
And as per the docs of Redux itself, the same concept as Redux can be implemented in RxJs using the scan operator and a couple of operators more, as we have just seen. Also, RxJs is already shipped with Angular.
So it would seem to make sense when coming across use cases that require Flux to implement them in RxJs instead of Redux, if by anything else by a matter of using a library we already have to know how in any case. Using a Redux implementation made in RxJs like ngrx is a good way to go too.
In the end, the solid concepts around Redux are more important than the library itself.
Alternatives
Another possible alternative to single atom of state applications is to build an application around the notion of observable data services.
Conclusions
It's still early for the Angular community to know what will a typical Angular application look like, and how state will be handled.
With all these options, its easy to loose sight of something important. There are for sure ways to do Flux-like apps in Angular, but the main question is do we really need a Flux-like architecture for our entire application? Or for only a certain screen or more advanced use case?
A good way to evaluate if Flux is beneficial is the React How-To guide, this question has some info too, and the original Flux talk.
References
Managing State in Angular Applications by Victor Savkin (@victorsavkin)
Want to Get Started With Angular?
If you enjoyed this article, we invite you to subscribe to the Angular University Newsletter (see box bellow).
If you want to learn more about Angular, have a look at the Angular for Beginners Course:
Other posts on Angular
If you enjoyed this post, here some other popular posts on our blog:
- Angular Router - How To Build a Navigation Menu with Bootstrap 4 and Nested Routes
- Angular Router - Extended Guided Tour, Avoid Common Pitfalls
- How to run Angular in Production Today
- How to build Angular apps using Observable Data Services - Pitfalls to avoid
- Introduction to Angular Forms - Template Driven, Model Driven or In-Between
- Angular ngFor - Learn all Features including trackBy, why is it not only for Arrays ?
- Angular Universal In Practice - How to build SEO Friendly Single Page Apps with Angular
- How does Angular Change Detection Really Work?