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.

RxJs subscribe callback functions

At this point, it is safe to assume that most Angular developers know about the following syntax. That’s how we subscribe to an Observable to receive its data:

If you’ve been reading this newsletter for long enough, you already know that there are better ways to subscribe to Observables and that there are no excuses for not using these techniques.

That said, it’s always good to know about all our available options. So here is another possible syntax:

The above syntax registers three different callback functions:

  • The first one is the same as in our first example – this is where the Observable data goes.
  • The second one is an error handler. Any exception thrown by the Observable would get caught by that callback.
  • The third callback function is invoked when the Observable completes, which means it won’t emit any more value.

Note that while that syntax still works (for now), it has been deprecated in favor of a more explicit syntax, where an object is passed. Each callback is attached to a named property, making the purpose of these callbacks more obvious:

Anti-pattern series: Not unsubscribing from Observables

We covered this in our newsletter before: Observables can cause memory leaks if we don’t unsubscribe from them.

Before Angular 16, there were a few different techniques available to unsubscribe automatically, the best of those being the async pipe (and yes, it’s possible to use the async pipe 100% of the time if you use the tricks highlighted here – no excuses).

With Angular 16, things are even better, as you can use the new takeUntilDestroyed operator as follows:

But here’s my million-dollar tip for today: Instead of using takeUntilDestroyed in your components, use it in the services that expose such Observables:

That way, whether you use an async pipe or not, your components are covered. That’s one of the nice things about Observables: We can change them whenever and wherever we want using pipe(), including in our services – so that all subscribers benefit from that change downstream.

mergeMap RxJs operator

A few months back, we covered the switchMap operator from RxJs. Today, let’s look at a similar operator called mergeMap:

The marble diagram isn’t as obvious as it is for other operators, so let’s clarify what mergeMap does:

  • It takes values from an observable and maps them to another observable
  • Then it allows us to combine the values from both observables into one new observable

What’s a real-life use case for mergeMap?

Let’s say you want to display a list of products with their associated categories. The product information is stored in one API endpoint, and the category information is stored in another API endpoint. To display the list of products with their categories, you need to make a separate API call for each product to retrieve its category information.

You can use mergeMap to combine the two API calls and merge the results into a single stream of data (product + category).

How is it different from switchMap?

mergeMap differs from switchMap in two important ways:

  1. switchMap only returns the data from observable #2 – mergeMap returns data from both observable #1 and #2 and allows us to combine that data any way we want
  2. switchMap cancels observable #2 whenever observable #1 emits a new value, then re-creates another observable #2. mergeMap doesn’t do that.

In other words, switchMap is perfect when we want to chain asynchronous actions such as “user selects a new filter, then we request new data according to that filter” because if the user changes that filter again, we want to cancel the previous request and start a new one.