This article explores how Angular applications can adopt a Functional Reactive style, leveraging the RxJs library that ships with Angular (see Managing State in Angular Applications by Victor Savkin (@victorsavkin). The discussion covers the following areas:
- The challenge of state management in single page applications
- Determining when a Flux-like architecture is appropriate
- Constructing a Flux-like Angular Application with RxJs
- Defining Application Actions
- Creating an Action Dispatcher with RxJs
- Establishing an Application State Observable
- Utilizing Observables through the async pipe
- Positioning RxJs: Smart vs Pure Components
- Contrasting with Redux
- Final Thoughts
For a deeper understanding of the RxJs operators referenced here, it is recommended to first review Functional Reactive Programming for Angular Developers - RxJs and Observables.
A key consideration is determining the right context for this architectural style. Further insight on this topic is available in the post Angular Service Layers: Redux, RxJs and Ngrx Store - When to Use a Store And Why ?.
If Observables are new to you, this post outlines common pitfalls encountered when working with them in Angular.
For those employing a centralized store with Ngrx, the following guide is relevant: 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
Not every single page application faces a significant state management challenge. Consider a basic CRUD tool for managing reference data like security privileges.
In such cases, using Angular Forms or NgModel is a straightforward and effective approach with minimal complexity.
However, there are known scenarios where this simple method is insufficient. The most well-known example is the unread messages counter issue at Facebook, which was the catalyst for the Flux architecture (see the original talk).
When is a Flux-like architecture needed?
As Pete Hunt (@floydophone), a core member of React, notes in the React How-To, you probably don't need Flux! It becomes necessary only when you encounter specific patterns. If your use case includes any of the following, a Flux-like architecture is likely warranted:
- the same data is displayed differently in multiple parts of the application, such as an email client showing a message in a list while also updating unread folder counts
- certain data can be modified by user actions in the UI and also by events from the backend via server push
- there is a requirement for undo/redo functionality, even if only for a subset of the application state
Depending on the application type, these use cases are not particularly rare.
An architecture for complex UIs
Consider a UI like Netflix, where a single movie may be present in several different lists. If you add a movie to your favorites and later see it in another list, it should appear as a favorite there as well. This illustrates the core need: when the same data appears in multiple UI locations, an action must consistently update the whole interface. This is a textbook scenario for Flux.
Is Flux/Redux the only solution for use cases like this?
Aside from the single atom of state approach detailed below, other options exist. For instance, you could structure the app around observable data services, which is essentially a variation of the Flux concept.
Regardless, if you find yourself in one of these situations within your app, how can a Redux-like single atom of state be implemented in Angular?
Building a Flux Angular app using RxJs
As with other Flux implementations, the process begins with UI Actions. These actions represent user interactions that multiple parts of the app need to respond to.
Application Actions
An action is a message describing an event in the UI, such as a Todo being added, removed, or toggled. To ensure type safety, a dedicated class is created for each action type:
Following that, a Typescript union type is defined to encompass all the action types:
These actions are essentially POJOs (Plain Old JavaScript Objects) that carry the data required for various application parts to adapt and update.
The next question is how to distribute a dispatched action to all the parts of the application that need it.
Using the Action Dispatcher
Actions are dispatched through an action dispatcher. This dispatcher is injected wherever actions need to be triggered, typically within Smart or Controller-like components such as the TodoList in the example application:
The dispatcher name inside the @Inject annotation is simply a token used to identify a specific injectable, which will be explained further. For now, it's sufficient to know that it can be used to dispatch any action to the rest of the application:
As will be shown, the dispatcher is built with just a few lines of RxJs. Before diving into its internal implementation, let's explore how other parts of the app react to an action.
Defining the application state
Let's begin by defining the structure of the application state:
The application state is composed of a list of todo items, which is the core data, along with an instance of UiState. A closer look at UiState follows:
UiState holds any non-data UI state, including the current message displayed to the user and a flag indicating whether a backend operation is in progress.
Introducing the Application State Observable
With the state structure defined, the next step is to conceptualize a stream that captures the state of the application over time: the application state observable.
This stream will emit new values in response to actions—todos will be added, toggled, deleted, and so on. This can be visualized as follows:
We will explore how to create such an observable shortly, using only a few lines of RxJs. Let's assume the application state observable already exists and can be injected anywhere within the application:
This allows any component that needs to respond to new state to have the observable injected and subscribe to it. The subscriber is decoupled from the specific action that triggered the state change; it is only aware that new state has arrived and the view must be updated accordingly.
How to use the application state observable
Subscribing to the application state observable works like subscribing to any other observable. For example, to extract the list of todos and make it available in the template, you could subscribe and populate a local member variable:
While functional, Angular provides a more elegant approach.
Consuming observables using the async pipe
The Angular async pipe offers a better way to consume observables. It automatically subscribes to the observable and returns its latest emitted value:
In this example, the list of todos is sourced from a todos observable. This observable is defined as a getter method in a controller class and is derived from the application state observable using the map operator:
As demonstrated, building a Flux app becomes straightforward once the dispatcher and application state observable are established:
- inject the dispatcher wherever an action needs to be triggered
- inject the application state observable wherever the app needs to react to new state
Let's now examine how to construct these two core elements with RxJs.
Building an Action Dispatcher
The dispatcher functions as a conventional event bus: it provides a way to trigger events and allows parts of the application to subscribe to the actions it emits.
The simplest implementation uses an RxJs Subject, which implements both the Observable and Observer interfaces. This allows us to both subscribe to it and emit values from it.
Making the dispatcher injectable
To make the dispatcher injectable, it needs an injection token. An InjectionToken is created for this purpose:
This token is then used to register a Subject in the Angular dependency injection system:
As a result, whenever the dependency injection system is asked for something named dispatcher, the Subject is injected.
Avoiding event soup while using the dispatcher
While the dispatcher is technically a Subject, its type on injection is marked as an Observer only:
This distinction encourages its use solely for dispatching events, for instance:
The goal is to prevent most application code from directly subscribing to the dispatcher, which would bypass the intended flow of subscribing to the application state.
There may be valid reasons to subscribe directly to the dispatcher, but more often this is unintentional. With the dispatcher defined, we can proceed to build the application state observable.
Building an application state observable using RxJs
First, an initial state for the application is defined, using an injection token named initialState:
When the name initialState is requested, the object defined above is injected: an empty list of todos and some initial non-data UI state.
Defining the application state
The application state observable is built as follows:
The application state is a function of the initial state and the ongoing stream of actions:
- the first value emitted by the state observable is the initial state
- after the first action occurs, the initial state is transformed, and the state observable emits this new state
- each subsequent action is applied, producing a new emitted state
Calculating the new application state
Each new state results from applying an action to the previous state, resembling a functional reduce operation. Therefore, the first step is to define a set of reducer functions, in the style of Redux. For all actions related to todos, the reducer looks like this:
This is a standard reducer function, taking a state and an action, and returning the next state of the todo list. A similar reducer, calculateUiState, is defined for the UiState portion of the state (see here).
Using reducers to produce a new application state stream
With reducer functions in place, the next step is to create a new observable stream that combines the actions stream and the initial state to produce the application state stream.
In a previous post, common RxJs operators were introduced. Here, the scan operator is used. It processes a stream and emits the output of a reduce function applied over time.
Specifically, it takes the current state, starting with the initial state, and continuously calculates the new state based on the previous state and the incoming action.
The output of scan is an observable that emits the various states of the application over time.
That's it! As the Redux docs mention, the core of Redux can be implemented with just a few lines of RxJs. However, there are a couple of remaining issues to address.
Avoiding reducer functions from being called multiple times for one action
During debugging, you might notice that the reducer functions are invoked multiple times for a single action when there are multiple consumers, such as several async pipes.
This occurs because each observable subscription creates its own independent processing chain, as detailed in this previous post.
While not inherently wrong, you might prefer, for simpler debugging, to ensure the reducers execute only once per action, as is the case in Redux.
To accomplish this, the second RxJs operator, the share operator, is introduced (see here for more details):
There is one more loose end, which concerns using the application state observable during application startup.
Ensuring that the application state observable can be consumed at application startup
There might be places in your application, during the initial setup where not everything is fully configured, where you'd like to inject and use the application state observable.
For instance, at startup time, as shown here. This can be problematic, as not all subscribers may be wired up when the first application state value is emitted.
The solution is to convert the application state observable into a stream that, upon subscription, always returns the last emitted value, even if that value was emitted before the subscription occurred.
To do this, the plain state observable is wrapped in a BehaviourSubject:
This ensures that subscribers will always receive at least the initial state value upon subscribing.
All Together Now
This is a complete example of constructing an application state observable (also see here):
This factory function can now be used to create an injectable application state observable:
This concludes the construction of the application state observable. Once this initial infrastructure is in place, the daily workflow involves primarily injecting the dispatcher and state where necessary and 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 into smart components. These components do not need local state variables, as they can utilize the state observable directly via the async pipe.
The point is not that the application is stateless, but that the application code itself manages no state. State management is delegated to the RxJs library, external to the application logic.
How should pure components use dispatcher and state
Pure components may accept observables as inputs, but they should avoid having the dispatcher or state observable injected. Doing so would tightly couple them to this specific application, hindering their reusability.
When a pure component needs to trigger an action, it should emit an event via EventEmitter. The smart component, which is assigned to this event, will then dispatch the corresponding action.
Comparison of building Angular apps with Redux
It's worth remembering that proficiency with RxJs is necessary for Angular anyway, as Observables are integral to APIs like Forms and Http.
And, according to the Redux docs, the core principles of Redux can be implemented in RxJs with the scan operator and a few others, as demonstrated. Furthermore, RxJs is included with Angular.
For use cases requiring a Flux architecture, implementing it with RxJs might be a sensible choice, since it's a library you already need to understand for Angular. Alternatively, a Redux implementation built with RxJs, like ngrx, is also a solid option.
Ultimately, the well-established concepts behind Redux are more important than any specific library implementation.
Alternatives
An alternative to a single atom of state is to design the application around observable data services.
Conclusions
The Angular ecosystem is still evolving, and a standard pattern for state management is yet to be established.
Given the available options, it's crucial to avoid getting lost and forgetting the bigger picture. There are several ways to build Flux-like applications in Angular, but the primary question remains: is a Flux-like architecture necessary for the whole application, or only for a particular screen or a more complex feature?
Useful resources for evaluating the benefits of Flux include the React How-To guide, this question, and the original Flux talk.
References
Managing State in Angular Applications by Victor Savkin (@victorsavkin)
Want to Get Started With Angular?
If you found this article useful, we encourage you to subscribe to the Angular University Newsletter (see box below).
To learn more about Angular, check out the Angular for Beginners Course:
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?
