
Exploring Observables in JavaScript Programming
One of the most powerful and popular JavaScript libraries that specializes in event processing is the Reactive Extensions for JavaScript library, or simply RxJS. RxJS uses the Gang of Four (GoF) design pattern named the Observable pattern as the basis for registering interest in an event, as well as doing something when an event has been triggered.
This article is an excerpt from my book, Mastering TypeScript, 4th Edition – A comprehensive guide to understanding TypeScript language and its latest features. In this article, you will explore Observables in the RxJS library. You can download the codes in this article here.
The RxJS library provides a simple mechanism for registering interest in an event, as well as a mechanism for generating events. Along with these basic principles of the Observer design pattern, RxJS provides a plethora of utility functions to transform event data, as and when it comes in. At the heart of the RxJS library is the concept of Observables, which are source event streams. This has given rise to using the term Observables to describe RxJS source streams, and what can be done to them. So when someone says use Observables, they really mean use the RxJS library with its source streams and utility functions.
Installing RxJS Library
To begin the discussion on Observables, let’s first install the RxJS library as follows:
The RxJS library already includes the declaration files that are needed by TypeScript, so there is no need to install them separately using @types.
To generate an Observable, we can use the of
function as follows:
import { of, Observable } from "rxjs";
const emitter : Observable<number> = of(1, 2, 3, 4);
Here, we start by importing the of
function and the Observable
type from the rxjs
library. We are then defining a constant variable named emitter
, which is using generic syntax to define its type as an Observable of type number
. We then assign the result of the of
function to the emitter variable, which will create an Observable from the numbers 1 through 4. We can now create an Observer as follows:
emitter.subscribe((value: number) => {
console.log(`value: ${value}`)
});
Here, we are calling the subscribe
function on the variable emitter
. As the emitter
variable is of type Observable, it automatically exposes the subscribe
function in order to register Observers. The subscribe
function takes a function as a parameter, and this function will be called once for each value that is emitted by the Observable. The output of this code is as follows:
value: 1
value: 2
value: 3
value: 4
Here, we can see that the function we passed into the subscribe function has indeed been called once for each value that is emitted by the Observable.
Note that only when calling the
subscribe
function on an Observable will the Observable start to emit values. Calling thesubscribe
function is known as subscribing to an Observable, and the values that are produced by the Observable are also known as the Observable stream.
The of
function has a partner function named from
, which uses an array as input into the Observable, as follows:
const emitArray : Observable<number> = from([1, 2, 3, 4]);
emitArray.subscribe((value: number) => {
console.log(`arr: ${value}`);
});
Here, we have a variable named emitArray
, which is of type Observable<number>
, and is using the from
function to create an Observable out of an array. Again, we call the subscribe
function on the Observable named emitArray
, and provide a function to be called for each value emitted by the Observable. The output of this code is as follows:
arr: 1
arr: 2
arr: 3
arr: 4
Here, we can see that the from
function has created an Observable stream from the array input, and that the function we provided to the subscribe
function is being called once for each value that is emitted by the Observable.
Pipe and Map
The RxJS library provides a pipe
function to all Observables, similar to the subscribe
function. This pipe
function takes a variable number of functions as parameters and will execute these functions on each value that is emitted by the Observable. The functions that are provided to the pipe
function are generally known as Observable operators, which all accept an Observable as input, and return an Observable as output. The pipe
function emits an Observable stream.
This concept is best explained by reading some code, as in the following example:
import { map } from "rxjs/operators";
const emitter = of(1, 2, 3, 4);
const modulus = emitter.pipe(
map((value: number) => {
console.log(`received : ${value}`);
return value % 2;
}));
modulus.subscribe((value: number) => {
console.log(`modulus : ${value}`);
});
Here, we start with an Observable named emitter
, which will emit the values 1
through 4
. We then define a variable named modulus
to hold the results of calling the pipe
function on the emitter
Observable. The only argument we are providing to the pipe
function is a call to the map
function, which is one of RxJS’ operator functions.
The map
function takes a single function as a parameter and will call this function for each value that is emitted by the Observable. The map
function is used to map one value to another, or to modify the value emitted in some way. In this sample, we are returning the result of applying the modulus of two to each value.
Finally, we subscribe to the Observable and log its value to the console. The output of this code is as follows:
received : 1
modulus : 1
received : 2
modulus : 0
received : 3
modulus : 1
received : 4
modulus : 0
Here, we can see that emitter
Observable emits the values one through four, and that the modulus Observable is emitting the modulus of 2
for each value received.
Note that in these code samples, we have not explicitly set the type for our Observables.
The emitter Observable and the modulus Observable could be explicitly typed as follows:
const emitter : Observable<number> = of(1, 2, 3, 4);
const modulus : Observable<number> = emitter.pipe(
...
);
Here, we have specified the type of both the emitter
Observable, and the modulus Observable. This is not strictly necessary, as the TypeScript compiler will determine the correct return types when working with Observables. It does, however, explicitly state what we are expecting out of the Observable stream, and in larger, or more complex Observable transformations, explicitly setting the expected return type makes the code more readable and can prevent errors.
Combining Operators
The pipe
function allows us to combine multiple operator functions, which will each be be applied to the values emitted by an Observable. Consider the following code:
const emitter = of(1, 2, 3, 4);
const stringMap = emitter.pipe(
map((value: number) => { return value * 2 }),
map((value: number) => { return `str_${value}` })
);
stringMap.subscribe((value: string) => {
console.log(`stringMap emitted : ${value}`);
});
Here, we have an Observable named emitter
that will emit the values 1
through 4
. We then have a variable named stringMap
that holds the result of the pipe
function on the emitter
Observable. Within this pipe
function, we have two map
functions. The first map
function will multiply the incoming numeric value by 2
, and the second map
function will convert it to a string, with the prefix str_
.
We then subscribe to the Observable and log each value to the console. The output of this code is as follows:
stringMap emitted : str_2
stringMap emitted : str_4
stringMap emitted : str_6
stringMap emitted : str_8
Here, we can see that both map
functions have been applied to each value emitted by the emitter
Observable. Note that we have actually modified the type of each value from type number to type string, in our second map function. This is why the type specified for the value parameter in our subscribe
function is of type string.
Summary
In this article, we have explored the basics of the RxJS library and the fundamental concept of Observables that it provides. We have seen how we can create Observables easily using the of
and from
functions. Learn more in the book Mastering TypeScript, Fourth Edition.
About the Author
Nathan Rozentals has been writing commercial software for over 30 years, in C, C++, Java and C#. He picked up TypeScript within a week after its initial release in October 2012 and realized how much TypeScript could help when writing JavaScript.
Credit: Source link