Combining multiple Observables with ngrxLet

Last month, I explained how to use ngrxLet to render a loading template. Earlier this year, I also covered how ngrxLet can be a great alternative to the async pipe. All in all, ngrxLet is a fantastic directive that keeps improving.

For instance, we can also use it to combine multiple different Observables into one local variable as follows:

Without ngrxLet, the equivalent code would have to use the async pipe syntax trick covered last week:

This also works, but it’s more verbose and has the downside of using ngIf, which is present only because we need a structural directive to enable the local variable assignment micro-syntax.

Use functions for readable RxJs operator chains

When you have complex RxJs operator chains, a good idea is to refactor them into simple functions. This can also favor code reuse for simple scenarios such as the following example.

Say we have a User object with the following shape:

We receive that object through an Observable; all we want from it is the user’s age. Using Rxjs (and date-fns), we could do something like this:

This works, but it’s not instantly apparent that we’re computing the age of the user, and if we need similar code elsewhere, we’d have to copy-paste that line of code, which is terrible.

Instead, we could create a function with an explicit name:

And then use it as our new custom operator function:

We can improve the signature of our function for type safety purposes, too:

You can see that code in action on Stackblitz here.

Obviously, the approach makes even more sense when you have several operators, and you can even combine such operator functions with other operator functions:

Now we have beautiful, maintainable code that can be read without documentation.

Reactive forms Observables

Last week, I gave you a simple decision framework to decide when to use reactive or template-driven forms.

As a matter of fact, the only reactive (meaning RxJs-friendly) piece of reactive forms is that FormGroup, FormArray, and FormControl all extend the AbstractControl class and, as a result, expose two different Observables:

Those two can be very powerful when used alongside some RxJs wizardry (see this tutorial on dynamic filtering with RxJs and Angular forms, for example). In their most simple form, we can use those to listen to changes in our form and react accordingly:

In the above example, we enable the city form control if a 5-digit zip code has been entered. Since we are subscribed to valueChanges, any future change of the zipcode value could enable/disable the city input.

The other Observable, statusChanges, focuses on validity changes only and returns one of the four following values:

Here is an example use case for statusChanges, which is more accurate than the previous example as this one would take into account all validation code relevant to the zip code:

Reactive or template-driven forms?

People are often conflicted when it comes down to forms with Angular. Should we use template-driven forms or reactive forms?

Here’s a simple answer:

  • If your form is dynamic (meaning: if the user selects this option, then add/enable or remove/disable other form elements) or will rely on RxJs for reactivity (meaning: auto-complete features or on-the-fly validation of user input using HTTP requests), then use reactive forms.
  • In all other scenarios, template-driven forms should be perfectly fine.

That’s it. A bonus for you: If you want to have custom validation of your form, use this tutorial to implement custom validation that works with both template-driven and reactive forms, so you’re not tied to a single option and can still change your mind later on.

RxJs retry() operator

Some weeks ago, I posted about how to handle errors in RxJs. In some cases, it might be a good idea to retry an Observable when it fails, especially for HTTP requests.

The retry operator has the following marble diagram:

In the above marble diagram, we retry the Observable 2 times. Since all attempts fail (the X in the timeline indicates an error), we still end up with an error at the end, but we can see that the first and second errors were invisible to the subscriber thanks to the retry operator.

The nice thing about retry is that we can specify a delay before retrying, as well as how many times to try again. By default, if we don’t use any parameter, retry will keep trying forever:

In the above example, we would retry a maximum of 3 times, wait 5 seconds between each attempt, and reset our retry counter if the Observable ends up succeeding. That way, another request in the future would still get all 3 attempts, for instance.

If you’re interested in a slightly more complex example using several more operators, I wrote that tutorial “How to do polling with RxJs and Angular?” that uses the retry operator.

Updates from ng-conf 2023

I’m writing this message up in the air over the desertic landscapes of Nevada as I’m flying back to California from ng-conf 2023. It has been two days filled with lots of information, and I will unpack some of the major announcements in more detail in the following newsletters.

For now, I’ll focus on some rapid-fire news grouped by topic:

RxJs

  • RxJs 8 is coming out soon, and it will be 30% smaller than RxJs 7 and 60% smaller than RxJs 6!
  • Will have support for async / await syntax for Observables.
  • We will have some new syntactic sugar for chaining operators using a rx() function. Code such as obs$.pipe(map(...), filter(...)) will be writable as rx(obs$, map(...), filter(...)). Optional, yet good to know.

NgRx

  • Support for a Signal Store is coming soon

Endbridge

Other cool news

  • Bard, Google’s response to ChatGPT, is built with Angular!
  • Analog.js is a meta-framework for Angular apps. It supports server-side rendering, static pages, back-end APIs, and regular Angular front-end code all in one project, all in Javascript. Similar to Next.js for React. Analog is still a work in progress but looks promising.

RxJs and Signals interoperability

Angular 16 introduced several features related to the brand-new Angular Signals, which include two functions enabling RxJs interoperability.

toObservable()

The name says it all. toObservable() takes a Signal and returns its data as an Observable:

This new Observable gets updated every time the underlying Signal value is updated. We can subscribe to it using the async pipe, for instance (demo code here):

toSignal()

toSignal() does the opposite of toObservable(). It turns an Observable into a Signal. Since Signals are different from Observables (a Signal always has a value right from the start – an Observable does not), the default behavior of toObservable() is to return a Signal that supports undefined as a default value:

This can be changed by providing an initial value as follows:

RxJs of() and from()

So far, we have covered several RxJs operators and information about Subjects, but what about Observables themselves?

There are a few utility functions that create Observables out of existing data. Such Observables immediately return the data and complete upon subscription.

Here is an example of such an Observable created with the of() function:

And an example created with the from() function:

The difference between of() and from() is that of() emits all the data right away in a single emission, whereas from() uses an array of items and emits them one by one with no delay between emissions.

In other words:

What’s the point of an Observable that just returns data instantly and does nothing asynchronous? First is testing. Being able to return to an Observable with hard-coded data is perfect for unit testing purposes as we can quickly validate different scenarios without using the actual HttpClient, for instance.

Second is caching data and returning it as an Observable (which can be done even more easily with a BehaviorSubject or ReplaySubject). For instance, say we have a service that retrieves a list of all countries in the world from a server. We know that that list doesn’t change every day, so we want to cache it so that if another component requests that data, instead of making another HTTP request, we return an Observable of that data:

Note the use of the tap operator to spy on the Observable, catch the data, and store it in our “cache” variable.

That way, the API is consistent and components always subscribe to an Observable, not knowing if the data comes from a server or a local cache.

Error Handling in RxJs

A few days ago, we saw that we could be notified of any error when subscribing using a specific callback function or that we could receive the same information using the tap operator.

There is another option available using a specialized operator called catchError and illustrated below under its deprecated name catch:

How does catchError help? As we can see in the marble diagram, it can return a different Observable when an error happens. This means that the subscriber would have no idea that something wrong happened as we are able to switch to a backup solution (perhaps some cached data or a different API endpoint).

This is especially important when you know that if an Observable throws an error, it will never emit another value in the future. This makes catchError an excellent option to recover or at least attempt to recover from an error in the browser. We will cover other options (such as operators that retry an Observable) in the subsequent editions of the newsletter.

RxJs TapObserver

In my last post, I covered how we can define multiple callback functions when subscribing to an Observable (next, error, complete). I mentioned that those callbacks are available when we call the subscribe() method, which goes against our philosophy of avoiding subscriptions as much as possible because they can lead to memory leaks.

In previous newsletters, I covered how the tap operator can help avoid subscriptions by “spying” on an Observable.

Well, tap is a simple gift that keeps on giving. The operator supports an TapObserver interface that can be used instead of the callback function we used in our earlier examples:

As a result, we can register several side effects in our code based on any of these notifications as follows:

Most of these notifications are explicit in what they do, but let’s document all six of them for the sake of clarity:

  • next: Invoked whenever the Observable receives new data
  • error: Invoked whenever an error occurs within the Observable
  • complete: Invoked when the Observable completes and stops emitting any new values.
  • subscribe: Invoked whenever a new subscriber subscribes to the Observable
  • unsubscribe: Invoked whenever a subscriber unsubscribes from the Observable
  • finalize: Invoked whenever the Observable is done, either because of an error, of completion, or unsubscription. It’s like a catch-all notification to mark the end of an Observable.

You can see an example of such TapObserver in action here on Stackblitz.