Types of Angular forms: Which fits where?

Forms are both the foundation and the most vulnerable part of websites. In a series of blogs about security, we’ve often written about how and why all inputs need to be treated, and that this is the most common way that cyber criminals use to hack into a web. Therefore, when choosing how to encode a form, you need to be careful. Fortunately, with Angular, for example, we have several options.

Template forms

This is the simplest type defined in a template, in which ngModel is used to bind data to the form. The code of such a form with name, surname, phone number and e-mail fields looks like this:

<form (ngSubmit)="onSubmit()" #myForm="ngForm">
  <div>
    <label for="name">Meno:</label>
    <input type="text" id="name" name="name" ngModel required>
  </div>
  <div>
    <label for="surname">Priezvisko:</label>
    <input type="text" id="surname" name="surname" ngModel required>
  </div>
  <div>
    <label for="phone">Telefónne číslo:</label>
    <input type="tel" id="phone" name="phone" ngModel required>
  </div>
  <div>
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" ngModel required>
  </div>
  <button type="submit" [disabled]="!myForm.form.valid">Odoslať</button>
</form>

At first glance, everything looks fine. However, the second and third glance may reveal that the form doesn’t check the input format, for example, whether there really are numbers in the phone number and whether the e-mail has an at sign. Therefore it’ll be sent with any error, no problem. These forms are thus suitable only for really small projects of a personal nature and I wouldn’t let them out into the world. 

If you want to use them anyway, I recommend predefined  (https://angular.io/api/forms/Validators) or even custom (https://angular.io/guide/form-validation) validators that treat the security of the forms. They’re available to programmers in the form of templates and can be deployed on both type and reactive forms. 

Reactive forms

With this type, FormControl and FormGroup are used to bind data to the form. The result looks something like this:

HTML Code:

<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
  <div>
    <label for="name">Meno:</label>
    <input type="text" id="name" formControlName="name">
  </div>
  <div>
    <label for="surname">Priezvisko:</label>
    <input type="text" id="surname" formControlName="surname">
  </div>
  <div>
    <label for="phone">Telefónne číslo:</label>
    <input type="tel" id="phone" formControlName="phone">
  </div>
  <div>
    <label for="email">Email:</label>
    <input type="email" id="email" formControlName="email">
  </div>
  <button type="submit" [disabled]="!myForm.valid">Odoslať</button>
</form>

Component code:

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

@Component({
  selector: 'app-form',
  templateUrl: './form.component.html',
  styleUrls: ['./form.component.css']
})

export class FormComponent implements OnInit {
  myForm: FormGroup;
  constructor(private formBuilder: FormBuilder) { }

  ngOnInit(): void {
    this.myForm = this.formBuilder.group({
      name: ['', Validators.required],
      surname: ['', Validators.required],
      phone: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]]
    });
  }
  onSubmit() {
    console.log(this.myForm.value);
  }
}

Here, some validators are incorporated into the code, so the user can’t send just about anything. For large projects, at least such basic type control is almost a necessity. Thanks to it, programmers know at any moment what type of data will arrive in the system via the field in the form. 

Various mechanics could also be used to extract data types from a regular template form too. But why do the extra work when we can reach for the reactive forms directly in the design, and do it properly and more effectively? ?

Make the code a little bit better and it would be ready to go out into the world. We could end at this point, but since version 14, Angular has prepared one interesting novelty for us, which is worth continuing.

Reactive type forms

So far, when extracting content from a form, “any” type has been assigned to the content. However, this doesn’t tell the programmer whether the new content will be a string, a boolean (yes/no), or a number. For example, if they want to feed additional logic into the form or work with the data further, they’d definitely lack this information.

However, reactive type forms make it possible to define them. In addition, even the IDE itself, in which the programmer writes the code, warns the developer immediately upon an unauthorized pairing.

Thus, the complementary code to the HTML from the previous reactive form looks something like this:

export class FormComponent implements OnInit {
    myForm: FormGroup<{
        name: FormControl<string>;
        surname: FormControl<string>;
        phone: FormControl<string>;
        email: FormControl<string>;
    }>;

    constructor(private formBuilder: FormBuilder) {}

    ngOnInit(): void {
        this.myForm = this.formBuilder.group({
            name: ['', Validators.required],
            surname: ['', Validators.required],
            phone: ['', Validators.required],
            email: ['', [Validators.required, Validators.email]],
        });
    }

    onSubmit() {
        console.log(this.myForm.value);
    }
}

Alternatively, you can use a descent where the return value for “.group” also sets the type for “myForm”, and improve it a bit:

export class FormComponent {

    myForm = this.formBuilder.group({
        name: ['', Validators.required],
        surname: ['', Validators.required],
        phone: ['', Validators.required],
        email: ['', [Validators.required, Validators.email]],
    });

    constructor(private formBuilder: FormBuilder) {}

    onSubmit() {
        console.log(this.myForm.value);
    }
}

As I mentioned, in such a case, IDE also performs a check over the code, which can come with useful errors such as: 

Or, in the case below, the string tries to paste into surnameCount, which is a number, however, because the programmer wanted to use it to inform about the length of the surname.

The error is corrected by “length”, which is the correct type for this value.

Reactive type forms are especially useful for programmers because they allow them to work strictly with the types they require. Therefore, it cannot happen that random undefined values enter the application and the project gets into an unpredictable, even dangerous state. Deploying types in forms can detect huge errors, as most sites on the web use forms. 

It’s also easier for programmers to code. Type checks won’t allow them to throw a string somewhere where a number should go, and cause a bigger bug at a later stage. The transpiler itself and the IDE will warn them, “Hello, you won’t pass through here and won’t start the web because you have a wrong format or you’re doing an unauthorized operation!”. 

Thanks to reactive type forms, we can therefore code “cleaner” and safer again. Thank you, Angular 14+!