Form Handling using RxJS and TypeScript
Tags: rxjs, typescript, webdev
Published: 2020-12-12 02:32:13

RxJS and TypeScript are becoming more and more popular these days. It's feasible to use both technologies alone or even as part of any JavaScript library or framework.

It's an interesting fact to know that the RxJS project is using TypeScript actively and it helped to find bugs when the library was migrating from JavaScript.

In this article, we'll see a _Reactive_ approach to handle HTML forms using TypeScript and RxJS.

### HTML Forms

HTML forms are widely used in Web Applications nowadays. They are essential every time you want to register a user, send a message through a contact form, or collect any data from your users. At the same time, the HTML vocabulary can help you to define a Form with simple syntax:

```html
<form>
   Add HTML Form Controls here: text input, checkboxes, etc
</form>
```

An HTML Form Control element can be used to receive an event or collect data within the HTML form context.

#### A simple Login Form

Let's get started with a simple form:

![login-form-rxjs-typescript](//images.ctfassets.net/zojzzdop0fzx/1OzKZpx2eCpfOlxd2cl2hL/36b19aa9e23bdbb931c5e6894799d0a1/login-form-rxjs-typescript.png)

This may be a simple form. However, there are different ways to handle it from a developer perspective.

### Form Definition
Let's consider the following login form implementation:

```html
<div class="container">
  <form class="form">
    <div class="form-group">
      <label for="email">Email address</label>
      <input type="email" class="form-control" id="email" />
    </div>
    <div class="form-group">
      <label for="password">Password</label>
      <input type="password" class="form-control" id="password" />
    </div>
    <button id="submit" type="button" class="btn btn-primary">Submit</button>
  </form>
</div>
```

The previous code uses [Bootstrap](https://getbootstrap.com/), which is a powerful CSS framework that contains several ready-to-use styles. 

### The TypeScript Model
TypeScript is all about good and safe typing. Let's be sure to define an _Interface_ for our data model:

```ts
// user.ts
export interface User {
  email?: string;
  password?: string;
}
```

Right after that, we can define a new variable to "contain" our User model:

```ts
let userModel: User = {};
```

### The DOM Elements
Since we have our data model defined, it is time to define the variables that allow us to access the DOM elements:

```ts
// DOM Elements - declaration
let buttonElement: HTMLButtonElement;
let emailInput: HTMLInputElement;
let passwordInput: HTMLInputElement;
```

These variables are ready to have a _reference_ to the HTML Form controls we defined before in the markup. Pay attention to the types for each variable. 

At some point in the code it will be necessary to obtain the elements of the DOM:

```ts
// DOM elements - assignation
buttonElement = document.getElementById("submit") as HTMLButtonElement;
emailInput = document.getElementById("email") as HTMLInputElement;
passwordInput = document.getElementById("password") as HTMLInputElement;
```

In the JavaScript world, we can use the `getElementById` method to get the _Element_ associated with the given `id` property. You may use the `querySelector()` method as an alternative too.

One important note to point here is that `document.getElementById` will return an `HTMLElement` object. This is an interface that represents any HTML element. 

This means we have a general type and it would be **better** to apply the [type assertion](https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions) from TypeScript:

```ts
// as-syntax
buttonElement = document.getElementById("submit") as HTMLButtonElement;

// angle-bracket syntax
buttonElement = <HTMLButtonElement>document.getElementById("submit");
```

### The DOM Events as Observables
One way to handle DOM events from JavaScript is through an [Event Listener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener). For example, to handle the _click_ event of the button we can do:

```js
buttonElement.addEventListener('click', function() {
  // Handle click event here
});
```

However, in the _Reactive Programming_ approach, we'll need to consider streams and [Observables](http://reactivex.io/documentation/observable.html). 

Let's continue with the definition of every event we can handle at this point: "input"(from HTML Input elements) and "click"(from HTML Button element) as follows:

```ts
// DOM events as Observables - declaration
let emailChange$: Observable<InputEvent>;
let passwordChange$: Observable<InputEvent>;
let submit$: Observable<MouseEvent>;
```

Pay attention (again) to the [Generic Types](https://www.typescriptlang.org/docs/handbook/generics.html#generic-types) used in every variable type. These _generic types_ will tell the TypeScript compiler the specific type of object that will "flow" through those observables. Also, by a code convention, the `$` sign is used at the end of the variable name to represent a **stream**.

We have the variables needed to assign the Observables. It's time to use the `fromEvent` operator from RxJS, which turns an event into an _Observable_:

```ts
// DOM events as Observables - assignation
emailChange$ = fromEvent<InputEvent>(emailInput, "input");
passwordChange$ = fromEvent<InputEvent>(passwordInput, "input");
submit$ = fromEvent<MouseEvent>(buttonElement, "click");
```

The `fromEvent` is a [creation operator](https://www.learnrxjs.io/learn-rxjs/operators/creation) and it's feasible to use it without the generic type:

```ts
submit$ = fromEvent(buttonElement, "click"); // Observable<Event>
```

The disadvantage is obvious: we will have a general type that might need the type assertion later to handle the object properly.

![input-event mouse-event](//images.ctfassets.net/zojzzdop0fzx/1i82Pu84Trh1uEqiM0fvcw/79aec468c336b4fd10491eeaabbd7168/input-event_mouse-event.png)

### Subscribe to Event-Observables
Let's start processing the email value as a stream:

```ts
emailChange$
  .pipe(map((event: InputEvent) => (event.target as HTMLInputElement).value))
  .subscribe(email => {
    console.log("email Value: ", email);
    userModel.email = email;
  });
```

This is what is happening:

* **emailChange$** is an Observable. Picture this: you have a data flow ready to be processed. How are you going to process them?  By using functions (_pipeable operators_ in terms of RxJS).
* **.pipe()** function provides a readable way to use operators together. For example, you could have a kind of "combination" of Linux commands: `ls -l | grep 'json | sort` where you're using `|` as a pipe to perform operations or actions one after another.
* **map()** operator comes first and allows to "extract" the value property from the `HTMLInputElement`(the input field).
* **event.target as HTMLInputElement** is needed since `event.target` will return the `EventTarget` interface by default, and the `HTMLInputElement` interface provides the methods and properties for `<input>` elements.
* **subscribe()** call is "needed" to call the Observable. It behaves similar to when you call a function.
* Once the _Observable_ is called, it returns the `email` value and the `User` model can be updated.

We can apply the same logic to process the password value changes:

```ts
passwordChange$
  .pipe(map((event: InputEvent) => (event.target as HTMLInputElement).value))
  .subscribe(password => {
    console.log("password Value:", password);
    userModel.password = password;
  });
```

See the final value is assigned to the `userModel.password` property.

May you guess a potential improvement here? The email and password value handling are the same. We can create a common function to avoid code duplication as follows:

```ts
function getValueFromInputEvent(
  event: Observable<InputEvent>
): Observable<string> {
  return event.pipe(
    tap(event => console.log("event.target", event.target)),
    map((event: InputEvent) => (event.target as HTMLInputElement).value)
  );
}
```

The previous function receives an `Observable<InputEvent>`, and returns the string value as an Observable too!

Let's apply that function on `emailChange$` stream:

```ts
emailChange$.pipe(getValueFromInputEvent).subscribe(email => {
  console.log("email Value: ", email);
  userModel.email = email;
});
```

This is a short version, and easier to read too. You can apply the same function to process `passwordChange$` stream.

Finally, we're ready to process the "click" event. Following the same logic, let's subscribe and process the **User** model at the end.

```ts
submit$
  .pipe(tap((event: MouseEvent) => console.log(event)))
  .subscribe(() => console.log("Sending User", { userModel }));
```

### Source Code Project
Find the complete project running in [StackBlitz](https://stackblitz.com/edit/rxjs-typescript-form?file=index.ts). Don't forget to open the browser's console to see the results.

You can follow me on [Twitter](https://twitter.com/luixaviles) and [GitHub](https://github.com/luixaviles) to see more about my work.

---
*This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit [thisdotlabs.com](https://www.thisdotlabs.com).*

*This Dot Media is focused on creating an inclusive and educational web for all.  We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit [thisdot.co](https://www.thisdot.co).*