Understanding RxJS and Functional Reactive Programming
This article covers the foundational ideas behind RxJS and Functional Reactive Programming (FRP). Although these concepts are largely framework-agnostic, we’ll also explore practical Angular use cases to ground the discussion.
A number of the principles discussed here are not tied to Angular specifically, yet we will touch on real Angular examples wherever they clarify the topic.
Table Of Contents
Here are the topics we'll be covering:
- A new asynchronous programming concept: the stream
- A new primitive type: Observables
- Functional Reactive Programming and RxJs
- The essential of how Observables work
- Commonly used operators: map, filter, reduce, scan
- Common uses of RxJs in Angular: Forms and Http
- The share operator and Hot vs Cold Observables
- How to approach the learning of RxJs
- Conclusions
If you're new to Observables and Angular, you may find this post helpful for dealing with some frequent issues.
Functional Programming in the Frontend World
Although Functional Programming (FP) has existed for decades, its adoption in mainstream development has been gradual. While certain best practices have become widely accepted, libraries that support true function composition have only recently appeared, such as the latest lodash with first-class FP support, or Ramda.
Frontend programming is inherently asynchronous, and there has always been a piece missing that would allow building frontends in a functional-style manner.
A new asynchronous programming concept - the stream
The missing notion that lies at the heart of Functional Reactive Programming is likely the stream.
A stream is simply a sequence of values occurring over time. Consider, for instance, a stream of numbers where each value is emitted every second:
0, 1, 2, 3 ,4
Another example would be a stream of mouse click events, each carrying the x and y coordinates of the click:
(100,200), (110, 300), (400, 50) ...
Nearly everything happening in the browser can be viewed as a stream: the sequence of browser events fired when a user interacts with the page, data arriving from a server, or timers going off.
Streams appear to be a good representation of how a frontend application actually behaves. But can we comfortably build a readable program around this concept?
A new asynchronous development primitive - The Observable
To make the concept of a stream useful for building programs, we need mechanisms to create streams, subscribe to them, respond to new values, and combine streams to form new ones.
Notice the resemblance between the numeric stream above and something you might already know:
[0, 1, 2, 3, 4]
It looks a lot like a plain Javascript Array!
Arrays are data structures that are easy to manipulate and combine into new arrays, thanks to their extended API. Consider all those data manipulation methods, and imagine that streams could also be combined using these and other functional programming operators.
Combining a stream with a set of functional operators for transforming streams leads us to the concept of the Observable. Think of it as your API for interacting with a stream. You can use it to define a stream, subscribe to it, and transform it.
One important point to keep in mind: observables are not streams—these are two separate notions. What we need at this stage is a library that implements the Observable primitive, and that's exactly where RxJs comes in.
Introducing RxJs
RxJs, short for Reactive Extensions for Javascript, is an implementation of Observables for Javascript.
To see it in action, here's the same numeric stream mentioned earlier, defined using RxJs:
This line creates an Observable that emits a value every second. It will emit five values, then complete and stop emitting any further values.
Understanding operators and the pipe syntax
The take(5) call is an example of using the take operator, one of many RxJs operators available.
An operator is a function that takes an Observable and returns another Observable. In this case, we take the interval(1000) Observable, which emits values every second indefinitely, and create a new derived Observable that emits values for only 5 seconds and then stops.
The pipe syntax draws an analogy between RxJs operators and Unix pipes: values from one observable are transformed by an operator to produce another observable, much like how in Unix a value is passed through a pipe to another process to produce a new value.
Now that we have the concepts of Stream, Observable, and operator, we can introduce the notion of Functional Reactive Programming.
Introducing Functional Reactive Programming
Functional Reactive Programming (FRP) is a software development paradigm suggesting that entire programs can be built solely around the concept of streams. This applies not only to frontend programs but to programs in general.
When developing in this paradigm, the work involves identifying or creating the streams of values your program cares about, combining them, and finally subscribing to those streams to react to new values.
The core goal of FRP
The central idea of FRP is to build programs declaratively, by defining what the streams are, how they are connected, and what happens when a new value flows in over time.
Programs built this way typically have very few or no application state variables, which are commonly a source of bugs. To clarify: the application does have state, but that state usually lives in certain streams or in the DOM, not in the application code itself.
Stateless UIs, but which part?
This absence of local state is mainly intended for smart components that have data services injected. Pure components may consume observables but often still need to keep some internal state in practice—for instance, an isOpen boolean representing whether a dropdown is expanded.
The essential of how Observables work
Let's revisit the simple numeric sequence Observable introduced earlier, this time adding a side effect:
Note that you probably want to avoid the
tap()operator since its only purpose is to produce side effects
If we run this program, you may be surprised to find that nothing prints to the console! This is due to one of the key properties of this type of Observable.
Observables are either hot or cold
If this plain Observable has no subscribers, it will not be triggered!
The observable is considered cold because it does not produce any values unless there is an active subscription. To see the numeric values displayed, we need to subscribe to the Observable:
obs.subscribe();
With this, the numeric values are printed to the console. But what happens if we add two subscribers to this observable?
In this scenario, the Observable named obs contains a side effect: it prints to the console via the tap() operator. Then, two subscribers are attached to obs, each also printing the received value. Here's the resulting console output:
obs value 0
observer 1 received 0
obs value 0
observer 2 received 0
obs value 1
observer 1 received 1
obs value 1
observer 2 received 1
It seems the side effect is executed twice! This brings us to another important characteristic of Observables.
Observables are not shared by default
When we create a subscriber, we are effectively setting up an entirely new, independent processing chain. The obs variable is just a definition—a blueprint describing how a functional processing chain of operators should be assembled from the event source up to the sink (the observer) when that observer is attached.
Since obs is merely a blueprint for building an operation chain, subscribing two observers results in two separate chains being created. Consequently, the side effect is triggered twice, once for each chain.
There are ways to define other kinds of Observables where the side effect would only run once (more on this later). The key takeaway is that when dealing with observables, you should always keep two questions in mind:
- is the observable hot or cold?
- is the observable shared or not?
Commonly used RxJs operators in Angular
Many functional operators are available for combining Observables, so let's focus on some of the most frequently used ones and how they apply within an Angular application.
Specifically, we'll see how these operators are useful in everyday tasks like form validation.
How does Angular use Observables
Angular currently uses RxJs Observables in two distinct ways:
- as an internal implementation mechanism for parts of its core logic, such as
EventEmitter - as part of its public API, particularly in Forms and the HTTP module
The map operator
The map operator is likely the most recognized functional programming operator, and Observables certainly include a version of it. The map operator takes an Observable and applies a transformation function to each value emitted from the stream. For example:
It's important to understand that the result of map is still an Observable. What we have here is only a definition of an operation chain. We still need to subscribe to this observable in order to receive output.
Map and filter used to do form validation
Another commonly used operator is filter. In Angular, forms can be treated as observables we subscribe to. This means the value of the entire form is an observable, and the value of each individual field is also an observable. Let's take a straightforward form as an example:
By using ngForm, we can bind the form to a variable of type FormGroup in the component controller. From there, we can access the form observable via form.valueChanges.
Using that observable, we can chain the map and filter operators to derive an uppercased and validated version of the form content:
Check out this post for more details on using NgFormModel to achieve this.
The reduce operator, and why you probably don't need it
There's been growing discussion around the Flux architecture and its application in Angular development—see, for instance, this earlier post. The core idea is to maintain a single atom of state for the entire application, subscribe to it, and produce new state values using reducer functions.
Central to this style of frontend development is the reduce functional operator, which also lies at the core of Redux. RxJs observables provide a reduce operator as well, so let's see how it works:
What's happening here is that, given the obs observable, we construct a second observable named reduced. The reduced observable emits a single value once the stream obs completes—that value being the total sum of all elements in the stream. The console output appears as:
total = 10
So reduce ends up emitting the final total of the accumulation, staying true to the functional definition of the operator. However, that's not quite what we want if the observable contained application state instead of numeric values.
The scan operator
You might be interested in the intermediate values produced during the reduction process, wanting to know the state of the observable after each element is accumulated, rather than reacting only to the final result. This becomes especially relevant because the reduced stream may never actually close!
That's precisely what the scan operator provides. It's central to building Redux-like applications with RxJs:
Once more, we created a second observable based on obs and subscribed to it. Here's the result:
0
1
3
6
10
As you can see, the console shows the intermediate results of the accumulation, not just the final value.
The share operator
One key property we noted earlier is that subscribing to an observable triggers the creation of a separate processing chain. The share operator allows us to share a single subscription of a processing chain among multiple subscribers. Take this example:
That yields the following output:
obs value 0
observer 1 received 0
observer 2 received 0
obs value 1
observer 1 received 1
observer 2 received 1
The side effect inside the tap call only runs once, rather than twice.
Learn about Angular Services, HTTP, and RxJs
All Together Now
It's entirely possible to build a Flux-like Angular application with a single atom of state, just like Redux, simply by using a few RxJs operators presented here. See this post: Angular Application Architecture - Building Flux apps with RxJs and Functional Reactive Programming.
Another alternative is to construct data services using observables—refer to
How to build Angular apps using Observable Data Services - Pitfalls to avoid for more details.
Conclusions
RxJS and FRP are powerful concepts that surface in various parts of the Angular API and can deeply influence how applications are architected, enabling a significantly different development style.
When structuring Angular applications, you have several paths. One approach is to go fully reactive, leaning heavily on RxJs. Another is to keep things simpler, using RxJs only where Angular exposes it through its public API, such as Forms and Http.
You can also take a hybrid route: adopting a reactive approach in parts of the app where a Flux architecture is beneficial (see here for guidance on when that might be), while using more conventional methods elsewhere.
Either way, and for building Angular apps in general, gaining some familiarity with RxJs is worthwhile.
How to approach the learning of RxJs
RxJs is sometimes said to have a steep learning curve. You can likely ease into it gradually, first focusing on a couple of core concepts: Observable laziness and hot versus cold observables.
After that, it's a matter of getting comfortable with the most common RxJs operators. Roughly 10 to 15 operators are typically enough to build most applications.
One practical approach is to experiment with RxJs using JsBin, taking it one concept or operator at a time.
Want to Get Started With Angular ?
If you're looking to learn more about Angular, check out the Angular for Beginners Course:
References
Managing State in Angular Applications by Victor Savkin (@victorsavkin)
The introduction to Reactive Programming you've been missing by Andre Staltz (@andrestaltz)
Further Reading on Angular
If you found this guide useful, you might also like these well-regarded articles from our publication:
- 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?
