Reactive Forms in Angular

Codemotion
9 min readDec 17, 2019

The Frontend talks are very popular, mainly because of the fast pace of the ever-evolving JavaScript and the entire web ecosystem related to it. Every day there is a new framework or a new state management to help us build cool new applications.

In Italy, there are many social groups, on Facebook, with over 14,000 members, about Angular, React and JavaScript in general. Behind these groups, there is a community leader, and I know what I am saying because if you saw his latest talk at Codemotion Rome 2019, you would have seen the main hall of the conference packed with people. Only standing places were left available to listen to Fabio Biondi’s talk, and nobody minded the hot temperature inside.

This talk was confirmation that Fabio Biondi is the leader of the Angular community in Italy. He is a Google Developer Expert, he works as a freelancer and is an Angular and React mentor. I do not know how he can find the time to manage his huge social groups, but he is always ready to help and reply to everybody that asks for help.

Every web application nowadays has many forms. They are part of any application, it doesn’t matter what technology you are working on or what kind of applications are you writing, at some point, you will need a form to ask for and collect data. This is a very boring and repetitive task, that every frontender, like me, wants to skip through as quickly as possible. In this paper, we are going to take a closer look at one of the most misunderstood ways to create forms in Angular.

Forms are a group of controls for gathering the data from the user.

There are two different approaches in Angular to build our form. The first category is Template-Driven Forms, where we use HTML syntax, while the second category is called Reactive Forms, based upon on Observable and RxJS library. The structure of a form is defined inside the Component class instead of the Template file definition.

RxJS is a library with a clear API to work with both asynchronous and synchronous code, thanks to pipeline operators and use the concept of Observable.

Template-Driven Form is the first approach we learn and often is the only one we use inside our application, because Reactive Form is a bit harder to understand and with Template Forms, we can handle almost everything. The syntax is very much like the classical HTML Forms, so it’s another reason why a lot of us are comfortable with this syntax.

Template-driven forms are driven by code in the template. This means that you will see directives like ngModel and ngForm in the template, which will build our form for us; they do the dirty work behind the creation of a form.

Reactive forms are also known as model-driven forms, where the form is explicitly defined in the component class and the HTML content changes depending on the code in the component.

Look at the following Template-Driven Form example:

<form #form="ngForm" (ngSubmit)="addUser(form.value)">
<img class="spinner" *ngIf="usernameRef.pending">
<input type="text"
name="username"
[ngModel]
#usernameRef="ngModel"
required
UsernameAsyncValidator
>
<button [disabled]="form.invalid || form.pending">Add User</button>
</form>

We can write and handle a form without writing any single line of JavaScript code by just using some directives and built-in validators, such as “required” or “max-length” or “min-length” or writing our own custom validator like “UsernameAsyncValidator” that you can see within the example.

Let us do a comparison between Reactive and TPL-Driven Forms:

A comparison between reactive and template driven forms

As you probably know, Angular is organised in Modules, so in order to use Template-Driven Form or Reactive Form, you need to import the FormsModule for the first one and the ReactiveFormsModule for the other one, both from the ‘@angular/forms’ package.

There are many differences between Reactive and Template-Driven forms, but the main difference is: Reactive is synchronous and TPL form is asynchronous.

The other reasons to use Reactive Forms are:

  1. Template-Driven forms are Asynchronous because of ngForm and ngModel, these directives take more than one cycle to build the entire control tree. That means you must wait for a tick before manipulating any of the controls from within the component class;
  2. Custom validators in Template-Driven forms need more work;
  3. As our template grows it becomes harder to understand the form structure and readability, so Reactive forms are more scalable than Template-Driven forms;
  4. Although Template-Driven forms are easier to create, they become a challenge when you want to do unit testing, because testing requires the presence of a DOM;
  5. Reactive forms are built with immutable data while Template-Driven forms promote the use of bidirectional binding (avoid the evil!).

Reactive Forms

The goal of this talk was to show the potential of Reactive Forms, in particular how they handle complex forms with a lot of validation.

Every control in the form (select, input, checkbox, etc…) has a valueChanges property, an Observable that we can subscribe to in order to observe changes over time and transform values with RxJS operators in a functional way. Therefore, a form is treated like a stream of data where every field acts like an Observable.

A Control Form with the property valueChanges

Let us look at an example of a Reactive Form:

import { Component, OnInit } from \'@angular/core\';
import { FormControl } from \'@angular/forms\';

@Component({
selector: \'app-login-form\',
template: `
<input type="text" [formControl]="name">
`
)
export class LoginComponent implements OnInit {
name = new FormControl(\'name\');
ngOnInit() {
this.name.valueChanges.subscribe(data => console.log(data));
}
}

As you can see, we have property declared as FormControl and we can initialise it with an arbitrary value and link it to the input form from the template. This way, they are always in sync. I can observe the value changes made in the template with an explicit subscription to the valueChange property or use the setValue method to change the value in the component and reflect it to the template. It acts like a bidirectional data binding.

In a Template-Driven Form, every form element is connected to the Model property (inside the Component logic) with a ngModel directive. This is the reason why TPL Forms are asynchronous. The directive ngModel is connected to the “change detection application life-cycle”, so the value between the HTML element and the component is updated after a “tick” of the event loop.

Template Driven Form with ngModel Directive

Thanks to the RxJS library, we can use the fromEvent method to Observe and subscribe to an event. Using the Angular ViewChild decorator and without using Reactive Forms, we can take a reference of an element of the DOM (like a getElementById) and take advantage of RxJS operators to manipulate the data inside an input element:

Using Viewchild decorator to access an element

Of course, you need to know a bit of RxJS, at least the base operators such as map, filter, take, debounceTime and distinctUntilChanged.

Until now, we have not used the basic constructor of Reactive Forms from FormBuilder like FormControl, FormGroup and FormArray. We can use them with FormBuilder or use them alone like in the previous example.

The next example is a bit harder, because we introduce FormGroup and FormBuilder to construct our form and to have a better scalability on our forms:

import { Component, OnInit } from \'@angular/core\';
import { FormControl, FormBuilder, FormGroup } from \'@angular/forms\';

@Component({
selector: \'app-login-form\',
template: `
<form [formGroup]="loginForm" (ngSubmit)="submit()">
<input type="text" [formControl]="username">

<button [disabled]="loginForm.invalid">Login</button>
</form>
`
})
export class LoginComponent implements OnInit {
loginForm: FormGroup;
username = new FormControl(\'username\');
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.loginForm = this.formBuilder.group({
username: this.username
});
this.loginForm.valueChanges.subscribe(data => console.log(data));
}

submit() {
console.log(this.loginForm.value);
}
}

Angular provides a service called FormBuilder that allows you to write less code. In order to use it, we need to inject it inside a Component via “Angular Dependency Injection”. Now we can use FormGroup to easily build our forms. Inside the FormGroup we have an object of key-value pairs, where the key is the name of the control and the value is an array of options such as the initial value, the built-in validators and some custom validators.

Custom Validator

A custom validator is a function that receives as a input the control where it’s applied and, inside the body, we can handle some verification code like a match to a Regular Expression.

Inside the component logic we have three ways to get a control reference:

  1. form.get(‘company’)
  2. form.controls[‘company’]
  3. form.controls.company

where “form” is the reference of my form. They are all equivalent. This is can be useful to control our HTML element inside the component logic.

Nested Form

A Nested Form Group is a group of input elements that we need to validate together. It allows us to create an array of objects:

Reactive Forms

Some working examples:

import { Component, OnInit } from \'@angular/core\';
import { FormBuilder, FormGroup, Validators } from \'@angular/forms\';

@Component({
selector: \'app-login-form\',
template: `
<form [formGroup]="loginForm" (ngSubmit)="submit()">
<input type="text" formControlName="username">

<input type="password" formControlName="password">
<button [disabled]="loginForm.invalid">Login</button>
</form>
`
})
export class LoginComponent implements OnInit {
loginForm: FormGroup;
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.loginForm = this.formBuilder.group({
username: [\'\', [Validators.required, Validators.minLength(2)]],
password: [\'\', [Validators.required, Validators.minLength(4)]],
});
this.loginForm.valueChanges.subscribe(data => console.log(data));
}

submit() {
console.log(this.loginForm.value);
}
}
import { Component, OnInit } from \'@angular/core\';
import { FormControl, FormBuilder, FormGroup, Validators } from \'@angular/forms\';

@Component({
selector: \'app-login-form\',
template: `
<form [formGroup]="form" (ngSubmit)="send()">
<input type="text" formControlName="username">
<div formGroupName="carInfo">

<input type="text" formControlName="model">
<input type="number" formControlName="kw">
</div>
<i class="fa fa-check" *ngIf="form.get(\'carInfo\').valid && form.valid"></i>
<button [disabled]="form.invalid">Login</button>
</form>
`
})
export class LoginComponent implements OnInit {
form: FormGroup;

constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.form = this.formBuilder.group({
username: [\'\', Validators.required],
carInfo: this.formBuilder.group({
model: [\'\', Validators.required],
kw: [\'\', Validators.required],
})
});
this.form.valueChanges.subscribe(data => console.log(data));
}

send() {
console.log(this.form.value);
}
}

We can also validate a field based on the value of another input element such as password matching form fields.

Every form group can have a second parameter where we can specify a custom validator for the form group itself. This validator is a function where we can pass the reference of the two password fields and do a matching check inside of it.

When our forms grow in complexity, we can split it in FormGroup or split our UI in Components. So, every FormGroup can be represented with a custom element:

Using Viewchild decorator to access an element

Dynamic Forms

With FormArray we are able to display element dynamically, it is a variant of FormGroup where data is serialised inside an array instead of an object like in FormGroup. This is useful when you don’t know how many controls will be hosted inside your dynamic forms.

Finally, we can generate a form at runtime based on a JSON structure that may come from an XHR REST call. The structure of the JSON can contain properties such as label, type (of the input), validators, etc.

Inside the template, we are going to use *ngFor directive and ngSwitch to select which input type we are going to render to the template.

The problem with this solution is that ngSwitch can grow, so we can define another solution using a Hash Map for our components that we can use to create forms template. For each control we can create a component. So, in the JavaScript code we have a cycle for reading the JSON configuration and build a form control at runtime, while for template we can use a *ngFor, and for every iteration we can use a directive called dynamicField that instantiates the right component for us, based on the configuration of the control.

As we can see this topic is huge and we can’t cover every aspect within a single paper like this, but I hope that I have awakened your curiosity.

--

--

Codemotion

We help tech communities to grow worldwide, providing top-notch tools and unparalleled networking opportunities.