Chapter 2: RxJS 7 – The Major Features

RxJS 7 was released on April 29, 2021. This new version came with a lot of improvements and new features over RxJS 6. Hence, we're going to use this version to benefit from the new features and guarantee optimized, high-quality code for our patterns. In this chapter, we will be discussing some of these features. We will only be covering certain features that we consider important and those that represent a major improvement over the features in RxJS 6.

We will begin this chapter by defining the bundle size and highlighting the importance of analyzing it. Then, we will compare the RxJS 6 and RxJS 7 bundles within an Angular app. After that, we will review some TypeScript typing improvements. Additionally, we will explain the reason behind the toPromise() deprecation and the newly offered alternatives. Finally, we will shed light on the API consistency improvements.

In this chapter, we're going to cover the following topics:

  • Exploring the bundle size improvements
  • Reviewing the TypeScript typing improvements
  • Understanding the toPromise() deprecation
  • Highlighting API consistency improvements

Technical requirements

This chapter does not require an environment setup or installation steps. All the code snippets here are just examples to illustrate the concepts. This book assumes that you have a basic understanding of Angular and RxJS.

Exploring the bundle size improvements

Every frontend application needs a number of JavaScript files to run. These files are either the ones you have written yourself or the external dependencies used to build your application, such as MomentJS, Lodash, or RxJS. A bundle is an output of a process that merges all of these files into a few (if not single) files in the most optimized way possible.

One of the major approaches commonly used to improve frontend performance is reducing the JavaScript bundle size. The smaller the size of the JavaScript bundle, the faster a page can load up the first time and be available to users.

The RxJS core team worked on reducing the bundle size of the library in version 7; they did a lot of refactoring to optimize the code and, consequently, the bundle size.

To get a better idea, let's measure the bundle sizes of two frontend applications using RxJS 6 and RxJS 7, respectively, and compare the size of the RxJS library in both applications. There are a lot of tools that can help you measure and analyze your bundles. The one that we are going to use is the source map explorer. The source map explorer enables you to analyze bundles that have a source map by offering a visual representation. In fact, it shows you an interactive tree map to help you determine where all the code is coming from, in addition to the size of the bundles that are emerging from both the external dependencies and the inner code.

The following figures represent the size of the RxJS 6 and RxJS 7 libraries within an Angular application, respectively. They have been captured using a generated source-map-explorer report.

Figure 2.1 – The size of the RxJS 6 library in a medium Angular app

Figure 2.1 – The size of the RxJS 6 library in a medium Angular app

Figure 2.2 – The size of the RxJS 7 library in a medium Angular app

Figure 2.2 – The size of the RxJS 7 library in a medium Angular app

As you can see, after the migration from RxJS 6 to RxJS 7, the bundle size of the library dropped from 69.72 KB to 49.84 KB without affecting the written code.

These results are generated from a medium Angular app. The difference between the bundle sizes of the two versions is quite prominent in larger applications. The size really depends on the RxJS concepts used; that's why it is different from one app to another.

On the other hand, operators come at a smaller cost. If we include every operator in RxJS 6, the bundled size of the operators will be around 52 KB, and in RxJS 7, it will be around 19 KB, even though RxJS 7 has more operators than RxJS 6.

Now that we know that RxJS 7 can be used to build apps with lower bundle sizes compared to RxJS 6, let's look at some of the other improvements of RxJS 7.

Reviewing the TypeScript typing improvements

RxJS 7 requires TypeScript 4.2 and takes advantage of its latest features. This helped the RxJS core team to improve the package types while making them stricter, more accurate, and more flexible. Providing better types can prevent runtime problems and any mismatched type confusion ahead of time.

For example, in RxJS 6, you can create a subject of a specific type and call next() without passing arguments; while, in RxJS 7, the same instruction will cause a compilation error because the subject's specified type has not been respected.

So, if you want to call next() without passing something to it, you need to make sure that your subject accepts void as a type:

//RxJs 6 example

const subject = new Subject<number>();

subject.next(); //compiles fine

//RxJS 7 example

const subject = new Subject<number>();

subject.next(); //compilation error

//RxJS 7 example

const subject = new Subject<void>();

subject.next();//compiles fine

This was an example of typing improvements in RxJS7, and there are others. Now, let's take a look at the toPromise() deprecation. We will start by giving a little bit of background. Then, we will explain the deprecation and the new alternatives.

Understanding the toPromise() deprecation

One of the major deprecations of RxJS 7 is the toPromise() deprecation. toPromise() will be deleted permanently after the release of RxJS 8. So, it is highly recommended that you avoid using toPromise() in your future developments.

Now, let's give a little bit of background to understand the reason behind this deprecation. It is important to understand the use case of toPromise(), why this particular method is available, and when to use it. Additionally, it is important to understand the incoherent behavior of this method in RxJS 6 in order to anticipate problems that might arise.

Promises are used by many developers nowadays, and there are thousands of promise-based libraries. A promise is an object that holds the result (or failure) of an asynchronous operation.

Promises have been available in JavaScript through third-party libraries such as jQuery. Additionally, ECMAScript 6 added built-in support to JavaScript for promises.

The common key point between observables and promises is that both objects could produce values over time. However, the major difference is that observables can produce none or more than one value, while promises only produce one value when resolved successfully.

The toPromise() method available in RxJS is a util method that is used to convert an Observable to a Promise. The toPromise() subscribes to the Observable and returns the last value emitted by this Observable.

In RxJS 6, when an error happens, toPromise() returns undefined. But this is not accurate!

That's because you cannot differentiate between the case where no value was emitted before completion of the observable and the case where an undefined value was emitted last.

Let's look at the first example in the following snippet. Here, toPromise()will return the last value emitted by the observable, which is World:

const source$ = new Observable<string>(observer => {

  observer.next('Hello');

  observer.next('World');

  observer.complete();

});

hello();

async function hello() {

  const value = await source$.toPromise();

  console.log(value);

}

//console output

//World

Now, let's change the preceding example and make the observable complete without returning any values. In this case, toPromise() will return undefined:

const source$ = new Observable<string>(observer => {

  observer.complete();

});

hello();

async function hello() {

  const value = await source$.toPromise();

  console.log(value);

}

//console output

//undefined

Now, let's make the observable return undefined as a value:

const source$ = new Observable<string>(observer => {

observer.next(undefined);  

observer.complete();

});

hello();

async function hello() {

  const value = await source$.toPromise();

  console.log(value);

}

//console output

//undefined

In this particular case, you cannot differentiate between the two situations where no value was emitted before completion and an undefined value was emitted last. A promise is a guarantee that a value will eventually be emitted; otherwise, the promise will throw an error.

When converting an observable into a promise, you might want to select which value to pick – either the first value that has arrived or the last one. Sometimes, you won't want to wait until the observable is completed; you need the first value as it arrives.

This was a confusing behavior, but luckily, it has been changed with the arrival of RxJS 7.

  • toPromise() is now deprecated in favor of firstValueFrom() and lastValueFrom().

The firstValueFrom() method

This method converts an observable into a promise by subscribing to the observable (which is given as a parameter) and returns the first value emitted by this observable. The subscription will be released immediately after returning the value. Additionally, it will reject with an EmptyError error if the observable completes with no values emitted:

import { firstValueFrom, lastValueFrom } from 'rxjs';

const source$ = new Observable<string>(observer => {

  observer.next('Hello');

  observer.next('World');  

  observer.complete();

});

hello();

async function hello() {

  const value = await firstValueFrom(source$);

  console.log(value);

}

//console output

//Hello

In the preceding code snippet, we created an observable called source$ that emits two values, Hello and World, and then completes. Calling firstValueFrom(source$) in the hello() function will return the first value emitted by the observable, which is Hello, and then completes.

The lastValueFrom() method

This method returns the last value emitted by an observable after its completion.

Also, it will reject with an EmptyError error if the observable completes with no values emitted:

import { firstValueFrom, lastValueFrom } from 'rxjs';

const source$ = new Observable<string>(observer => {

  observer.next('Hello');

  observer.next('World');  

  observer.complete();

});

hello();

async function hello() {

  const value = await lastValueFrom(source$);

  console.log(value);

}

//console output

//World

In the preceding code snippet, we created an observable called source$ that emits two values, Hello and World, and then completes. Calling lastValueFrom(source$) in the hello() function will return the last value emitted by the observable after the completion, which is World.

Empty errors

Now, let's bring our attention to a scenario where the observable does not return any values at all. Calling lastValueFrom(), as illustrated in the following code snippet, or firstValueFrom() will throw an error with a default message of no elements in sequence:

import { lastValueFrom } from 'rxjs';

const source$ = new Observable<string>(observer => {

  observer.complete();

});

hello();

async function hello() {

  const value = await lastValueFrom(source$);

  console.log(value);

}

//console output

//Error: no elements in sequence

So, the default behavior, when there is no value, is to throw a specific kind of error: EmptyError. However, if you want to customize this behavior, you can use the defaultValue parameter. The defaultValue parameter will be used to resolve a promise when the observable completes without emitting a value:

Const source$ = new Observable<string>(observer => {

  observer.complete();

});

hello();

async function hello() {

  const value = await lastValueFrom(source$, {

                                defaultValue: 'DEFAULT' });

  console.log(value);

}

//console output

//DEFAULT

Note

The lastValueFrom() method is recommended when you're sure an observable will complete. The firstValueFrom() method is recommended when you're sure an observable will emit at least one value or complete.

Well, this is all that you need to know about the toPromise() deprecation. It is time to move to the next improvement regarding API consistency.

Highlighting API consistency improvements

RxJS 7 also ships with a fix of some weird behavior in the library. Let's consider the example of a concat operator:

"Concat: Creates an output observable, which sequentially emits all values from the first given observable and then moves on to the next."

Let's consider the following sample:

    concat(

    of(1).pipe(

      tap(() => console.log('tap 1')),

      delay(100),

      finalize(() => console.log('Finalize 1'))

      ),

    of(2).pipe(

      tap(() => console.log('tap 2')),

      delay(100),

      finalize(() => console.log('Finalize 2'))

      )

    )

    .subscribe(x => console.log(x));

//console output

tap 1

1

tap 2

Finalize 1

2

Finalize 2

As you might have gathered, the finalization takes place after we subscribe to the next observable in the chain. This means that RxJS 6 didn't wait for finalization before subscribing to the next observable. So, finalize doesn't happen in the correct order.

In RxJs 7, this behavior has been fixed to respect the proper order:

    concat(

    of(1).pipe(

      tap(() => console.log('tap 1')),

      delay(100),

      finalize(() => console.log('Finalize 1'))

      ),

    of(2).pipe(

      tap(() => console.log('tap 2')),

      delay(100),

      finalize(() => console.log('Finalize 2'))

      )

    )

    .subscribe(x => console.log(x));

//console output

tap 1

1

Finalize 1

tap 2

2

Finalize 2

In the same way, the concat operator was deprecated in RxJS7 and replaced with concatWith. It will be removed in v8. For further details, please refer to the official documentation at https://rxjs.dev/api/operators/concat.

There are a lot of other improvements delivered in RxJS 7, namely, better memory usage as the majority of operators no longer retain outer values, a lot of bug fixes and documentation improvements, the consolidation of multicasting operators, renaming, and the deprecation of many operators.

We won't walk through all of those improvements as it is not the principal goal of this book. We can provide further details via the following official links:

Summary

In this chapter, we walked you through the main highlights of RxJS 7. We provided various categories of improvements with an example in each category. Now that we have got the basics right, it is time to start preparing and explaining the application that we are going to build in the next chapter, where we are going to implement all of the reactive patterns we have learned.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.47.163