In this post, we are going to go over the main concepts of RxJs and Functional Reactive Programming in general.
Many of the concepts in this post are not Angular specific, but we will be giving some practical Angular examples as well.
Table Of Contents
Let's go over the following topics:
- 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
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 troubleshooting scenarios.
Functional Programming in the Frontend World
Even though Functional Programming (FP) has been around for many years, the adoption has been slow in mainstream development. Some of its best practices are today more or less consensual, but for example only recently we started to see libraries capable of true function composition, like the latest lodash with first class FP support, or Ramda.
Frontend programming is inherently asynchronous, and there has always been something missing to allow the building of frontends in a functional-like way.
A new asynchronous programming concept - the stream
The notion that is missing and which is the heart of Functional Reactive Programing might very well be the
A stream is a sequence of values in time, its as simple as that. Take for example the following stream of numeric values, where each value is issued every one second:
0, 1, 2, 3 ,4
Another example of a stream is a sequence of mouse click events, with the x and y mouse coordinates of the mouse click:
(100,200), (110, 300), (400, 50) ...
Everything that happens in the browser can be seen as a stream: the sequence of browser events that are triggered when the user interacts with the page, data arriving from the server, timeouts getting triggered.
Streams seem to be a good model to describe how a frontend program actually works. But is it possible to comfortably build a readable program around that notion?
A new asynchronous development primitive - The Observable
In order for the notion of stream to be useful to build a program, we need a way to create streams, subscribe to them, react to new values, and combine streams together to build new ones.
Also notice the similarity of the numeric stream above to something more familiar:
[0, 1, 2, 3, 4]
Arrays are data structures that are really easy to manipulate and combine to produce new arrays, thanks to its extended API. Look at all those data manipulation methods, and imagine that streams could be combined using these and many other functional programming operators.
This combination of a stream with a set of functional operators to transform streams leads us to the notion of Observable. Think of it as your API for tapping into a stream. You can use it to define a stream, subscribe to it and transform it.
An important thing to bear in mind is that observables are not streams, those are two different concepts.What we need at this point is a library that implements the Observable primitive, and thats where RxJs comes in.
To see it in action, here is the same numeric stream that we mentioned above, defined using RxJs:
This line of code defines an Observable that emits values every second. Five values will be emitted, and then the Observable will complete and no more values will be emitted.
Understanding operators and the pipe syntax
take(5) call is an example of how to use the
take operator, which is one of the 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, that emits values every second forever, and we create a new derived Observable that only emits values for 5 seconds and then stops.
The pipe syntax makes an anology of how RxJs operators and Unix pipes are somewhat similar: the values of one observable are transformed by an operator to produce another Observable, just like in Unix a value is transformed via a pipe by another process in order to produce a new value.
Now that we have the notions of Stream, Observable and operator, we are now ready to introduce the notion of Functional Reactive Programming.
Introducing Functional Reactive Programming
Functional Reactive Programming (FRP) is a paradigm for software development that says that entire programs can be built uniquely around the notion of streams. Not only frontend programs, but any program in general.
While developing in this paradigm, development consists of creating or identifying the streams of values your program is interested in, combining them together and finally subscribing to those streams to produce a reaction to new values.
The core goal of FRP
The main idea of FRP is to build programs in a declarative-only way, by defining what are the streams, how they are linked together and what happens if a new stream value arrives over time.
Programs such as this can be built with very little to no application state variables, which are in general a source of errors. To make it clearer: the application does have state, but that state it's typically stored on certain streams or in the DOM, not on the application code itself.
Stateless UIs, but which part?
This absence of state is meant mostly for smart components that have data services injected. Pure components can consume observables but might want to keep some internal state variables in practice, think for example an
isOpen flag for a dropdown.
The essential of how Observables work
Let's go back to the simple numeric sequence Observable presented before, and add a side effect to it:
Note that you probably want to avoid the
tap()operator as its only purpose is to produce side effects
If we run this program, you might be surprised by the fact that nothing gets printed to the console! This is because of one of the main 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 said to be cold because it does not generate new values if no subscriptions exist. To get the numeric values printed to the console, we need to subscribe to the Observable:
With this, we do get the numeric values printed to the console. But what happens if we add two subscribers to this observable:
What's happening here is that the Observable named
obs has a side-effect: its prints to the console via the
tap() operator. Then two subscribers are added to
obs, each one printing the result value to the console as well. This is the output of the console:
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
Looks like the side effect is being called twice! This leads to a second important property of Observables.
Observables are not shared by default
When we create a subscriber, we are setting up a whole new separate processing chain. The
obs variable is just a definition, a blueprint of how an a functional processing chain of operators should be set up from the source of the event up until the sink of the event, when that sink (the observer) is attached.
obs just a blueprint of how to build an operation chain, what happens when we subscribe two observers is that two separate processing chains are set up, causing the side effect to be printed twice, once for each chain.
There are ways to define other types of Observables where the side-effect would only be called once (see further). The important is to realize that we should keep two things in mind at all times when dealing with observables:
- is the observable hot or cold?
- is the observable shared or not?
Commonly used RxJs operators in Angular
There are many functional operators that can be used to combine Observables, so let's focus here on some of the most commonly used ones and how they can be used in an Angular application.
Namely, we are going to see how some of those operators can be used in common tasks like form validation.
How does Angular use Observables
Angular currently uses RxJs Observables in two different ways:
- as an internal implementation mechanism, to implement some of its core logic like
- as part of its public API, namely in Forms and the HTTP module
The map operator
The map operator is probably the most well-known functional programming operator out there, and Observable of course provides it. The map operator simply takes an Observable, and adds a transforming function that processes the output of the stream. For example:
It's important to realize that the output of
map is still another observable. What we have here is still only a definition of an operation chain. We still need to subscribe to this observable to get an output out of it.
Map and filter used to do form validation
Another commonly used operator is
filter. In Angular, forms can be handled as observables that we subscribe to. Meaning that the value of the whole form is itself an observable, and that the value of each individual field is itself an observable. Let's take for example a simple form:
The use of
ngForm allows binding the form to a variable of type
FormGroup in the component controller. There we can use that variable to access the form observable via
Using the observable, we can combine the map and filter operations to get an uppercase and validated version of the form content:
See this post for further details on how to use
NgFormModel to do this.
The reduce operator, and why you probably don't need it
There has been some talk recently on the Flux architecture, and how to use it to build Angular applications, check for example this previous post. The main idea is to have a single atom of state for the whole application, subscribe to it and create new values of it using reducer functions.
Crucial to that way of building frontends is the
reduce functional operator, which is at the heart of Redux. RxJs observables also provide a reduce operator, so let's see how that looks:
What is happening here is that given the
obs observable, we create a second observable named
reduced. Reduce emits a value when the stream
obs closes and whose single value is the total sum of all elements in the stream. The output in the console is this:
total = 10
So reduce emits the end total of the accumulation, which is true to the functional definition of the operator. But this is not what we want in the case that observable contained the application state instead of a numeric value.
The scan operator
You might be interested in the intermediate values of the reduce process, and might want to know what is the state of the observable after each element is reduced and react to that instead of only to the final result of the reduction operation. Especially because the reduced stream might never close!
This is what the scan operator does, and its at the heart of how we can build Redux-like applications using RxJs:
Again we created a second observable based on
obs, and have subscribed to it. The result is:
0 1 3 6 10
We can see in the console the intermediate results of the accumulation process, and not only the final result.
The share operator
One important property we saw at the beginning of this post, is that when we subscribe to an observable, it triggers the instantiation of a separate processing chain. The
share operator allows us to share a single subscription of a processing chain with other subscribers. Take for example the following:
This creates 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
We can see the side effect inside the
tap call is only printed once instead of twice.
Learn about Angular Services, HTTP, and RxJs
All Together Now
It's possible to implement a Flux-like Angular application with a single atom of state in every way similar to Redux, by simply combining a couple of RxJs operators presented here, see this post Angular Application Architecture - Building Flux apps with RxJs and Functional Reactive Programming.
Another alternative is to build data services using observables, see
How to build Angular apps using Observable Data Services - Pitfalls to avoid for further details.
RxJS and FRP are really powerful concepts that are exposed in certain parts of the Angular API, and that can be used to structure applications to be built in a very different way than before.
There are multiple choices available to structure Angular applications. One option is to go full reactive in which case you will do extensive use of RxJs, but another option is to keep it simpler and use RxJs only via the parts of Angular that expose it in its public API (for example Forms and Http).
You can also mix and match: use a reactive approach in parts of the application where a Flux architecture is beneficial (see here for when to use it), and a more traditional approach elsewhere.
In both cases, and when building Angular apps in general it's important to gain some familiarity with RxJs.
How to approach the learning of RxJs
It's sometimes mentioned that RxJs has the potential for a large learning curve. This can likely be approached gradually, by focusing first in the couple of main core concepts: Observable lazyness and hot vs cold observables.
Then its a matter of focusing on the most commonly used RxJs operators, there are probably around 10 to 15 operators that are sufficient to build most programs.
One way to do it is to try RxJs using JsBin, trying one concept / operator at a time and take it from there.
Want to Get Started With Angular ?
If you want to learn more about Angular, have a look at the Angular for Beginners Course:
The introduction to Reactive Programming you've been missing by Andre Staltz (@andrestaltz)
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?