Understanding Angular Reactive Forms

Reactive forms provide a model-driven approach to handling form inputs whose values change over time. This guide shows you how to create and update a simple form control, progress to using multiple controls in a group, validate form values, and implement more advanced forms.

an in-depth article with practical examples!

“Reactive forms provide a model-driven approach to handling form inputs whose values change over time.”
Even though Angular Reactive Forms might seem complicated in a first approach, they can be quite awesome when you actually get it. They are really powerful and my intention here is to show a little bit of the most important things related to them that can be really useful in real world applications.

Firstly, in order to use them, you need to import ReactiveFormsModule into the desired Module.

import { ReactiveFormsModule } from ‘@angular/forms’;

Form Control

A Form Control is a class which holds the value and validation status of each propertie of your form. Roughly, each ‘input’ in your form will be associated to a Form Control.

const myFormControl = new FormControl();

When creating a Form Control, you can pass arguments to the constructor.

Below, I have a Form Control initialization with some of the most useful.

const myFormControl = new FormControl(
    { value: 'My String Value', disabled: true },
    [ Validators.required, Validators.maxLength(30)]
);

The first parameter, is the *FormState, *in which we can set the Form Controlinitial value and if it should be disabled initially.

The second parameter is an array of Validators, which set that Form Controlas **invalid **when its validation does not pass. For instance, on the example above, the Form Control is a **required **field and its max lenght is 30. If the inputted value does not fit those requirements, the Form Control state will be set as invalid. And when it does, it’ll be set as valid.

Angular comes with some pre-built Validators that will fit most needs, which can be found here: https://angular.io/api/forms/Validators.

However, sometimes you’ll need some specific validation and those validators just won’t do. For those cases, you can create your own Custom Validator. I’ll talk about that further in the article.

Form Group

A Form Group contains a group of Form Controls. It may also have nested Form Groups. They also contain informations about the validation status of the group as a whole.

const formGroup = new FormGroup({
    'name': new FormControl('', [ Validators.required ]),
    'email': new FormControl('', [ Validators.required ]),
    'address': new FormGroup({
        'zipCode': new FormControl(''),
        'street': new FormControl(''),
        'number': new FormControl('')
    )
});

For instance, on the example above, the status of the Form Group will only be valid when the name and email properties have been filled.

Having created a Form Group in our component, we can now bind it to a Form in our HTML. The example below shows how this can be accomplished:

<form [formGroup]="formGroup" >
  <h1>User Profile Form</h1>
  <input required
    type="text"
    placeholder="Full Name"
    formControlName="name"
  />
  <input required
    type="text"
    placeholder="E-mail"
    formControlName="email"
  />
  <div formGroupName="address" >
    <input required
      type="text"
      placeholder="Zipcode"
      formControlName="zipcode"
    />
    <input required
      type="text"
      placeholder="Street"
      formControlName="street"
    />
    <input required
      type="number"
      placeholder="Number"
      formControlName="number"
    />
  </div>
</form>

As shown, we first bind the form to the Form Group created. Then, for each input we bind a Form Control. And since we have a nested Form Group, we have to set a region for it (the *formGroupName), *letting Angular know that the Form Controls in that area are inside that nested Form Group.

Reading a Form Control Value

Using Form Groups’ method *get *you are able to select a specific Form Control in order to read its value or call some of its methods. Below is how you’d read its values and further we’ll be diving into some of these methods that you can use.

const inputtedName = formGroup.get('name').value;
const inputtedZipCode = formGroup.get('address.zipCode').value;

Also, in your HTML:

<p>Inputted Name: {{ formGroup.get('name').value }}</p>
<p>Inputted ZIP: {{ formGroup.get('address.zipCode').value }}</p>

Form Array

A *Form Array contains an array of Form Controls, Form Groups or even Form Arrays. It is a basic but essential structure for handling arrays with Angular Forms. Supposing you want to collect a user’s phone numbers, allowing him to input both a primary number and a secondary number. This is how you could declare a phones *Form Array in your Form Group:

const formGroup = new FormGroup({
    'phones': new FormArray([
        new FormGroup({
            'number': new FormControl('', [ Validators.required ]),
            'type': new FormControl('Primary')
        }),
        new FormGroup({
            'number': new FormControl(''),
            'type': new FormControl('Secondary')
        })
    ])
});

Then, this is how you’d set up your markup, providing a loop which will be rendered for each element in the Form Array:

<form [formGroup]="formGroup" >
  <h1>User Phones</h1>
  <div class="phones"
    *ngFor="let phoneGroup of formGroup.get('phones')['controls'];
            let i = index"
    formArrayName="phones"
  >
    <ng-container [formGroupName]="i" >
      <p>Phone Type: {{ phoneGroup.get('type').value }}</p>
      <input
        type="tel"
        placeholder="Phone number"
        formControlName="number"
      />
    </ng-container>
  </div>
</form>

Updating Form Values

The*** setValue*** and patchValue are methods from the Angular Reactive Forms which allows you to set the values of the Form Group controls.

With ***setValue ***you must provide the complete object, which will look for all the Form Group’s properties and overwrite the Form Controls’ values. If it is not provided an object containing all the Form Group properties it won’t work. Below is an usage example.

const formGroup = new FormGroup({
    'name': new FormControl('', [ Validators.required ]),
    'email': new FormControl('', [ Validators.required ]),
    'address': new FormGroup({
        'zipCode': new FormControl(''),
        'street': new FormControl(''),
        'number': new FormControl('')
    )
});
const updatedUserInfo = {
  'name': 'Leonardo Giroto',
  'email': 'leonardo@email.com',
  'address': {
    'zipCode': '22630-010',
    'street': 'Avenida Lucio Costa',
    'number': 9999
  }
};
formGroup.setValue( updatedUserInfo );

On the other hand, the ***patchValue ***method can be used to overwrite any set of properties that have been changed on the Form Group. Therefore, you can pass only some of the fields when updating the Form Controls’ values.

Below is an usage example, when we use it to update only the address portion of our Form Group.

const formGroup = new FormGroup({
    'name': new FormControl('', [ Validators.required ]),
    'email': new FormControl('', [ Validators.required ]),
    'address': new FormGroup({
        'zipCode': new FormControl(''),
        'street': new FormControl(''),
        'number': new FormControl('')
    )
});
const newUserAddress = {
  'zipCode': '01306-001',
  'street': 'Rua Avanhandava',
  'number': 999
};
formGroup.patchValue({
  'address': newUserAddress
});

It is also very important to notice that the setValue method can be used not only in **Form Groups **as a whole but also on the Form Controls. Therefore, you can use it to update a single control value by directily calling it on the Form Control, as shown below.

formGroup.get('email').setValue('new_email@email.com');

Form Validation Status

When creating a Form Group with Validators, as shown previously, it will contain information about the current Form Validation Status, according to the values its Form Controls hold. For instance, if we have *required *Validators on Form Controls and all values are empty, the form will be set as invalid.

const isFormValid = formGroup.valid;
const isEmailValid = formGroup.get('email').valid;

Above is how you’d check whether a Form Group is valid or not. It is important to notice you also have a validation status for each Form Control. Therefore, the **Form Group Validation Status is *invalid *if any of the Form Control Validation Status are invalid.

Another interesting feature is the option to manually set erros. If by any chance you need it in your flow to manually set a Form Control as *invalid *regardless of the Validators, that is how you’d do it:

formGroup.get('email').setErrors({ 'incorrect': true });

This change will update the Form Control Validation Status and therefore its Form Groups’. Also, you can also clear the **Form Controls’ **errors by setting them to null, as below:

formGroup.get('email').setErrors(null);

Touched & Dirty

“You may not want your application to display errors before the user has a chance to edit the form. The checks for dirty and touched prevent errors from showing until the user does one of two things: changes the value, turning the control dirty; or blurs the form control element, setting the control to touched.”
Checking for a **Form Controls’ touched **and **dirty **status are pretty straight forward, as in the following examples:

const isDirty = formGroup.get('email').dirty;
const isTouched = formGroup.get('email').touched;

Watching for Changes

A very interesting feature is the ability to *subscribe *to changes on the Form Group or a Form Control. The idea is pretty simples: you set a subscription and every time the value on the form (or control) changes the callback function is triggered and you can have logic set there. This is how you’d set those subscriptions:

formGroup.valueChanges.subscribe((val) => {
   // The form has changed. Insert here your logic!
});

A practical example I find it very useful is for address searching APIs integration. Supposing you have an input for the user to provide its Zip Code. You can add a subscription to its control and as soon as you have received a valid Zip Code, you can call the address search API and with its response fill the other address fields; address, city, neighborhood, etc.

formGroup.get(
  'address.zipCode'
).valueChanges.subscribe(async (val) => {
  const fullAddress = await _service.getFullAddress(val);
  formGroup.get('address.street').setValue(fullAddress.street);
});

Custom Validators

As I’ve said previously, it is possible to create your own Custom Validator to serve specific purposes. Roughly, a *Validator *is a function that receives a Form Control as a parameter and returns *null *if it’s valid or an error object if it isn’t.

Below is an example of a Custom Validator. It was created to validate if the provided *CPF number *(a brazilian document) is a valid one.

public static CPFValidator(control) {
  if (!control || !control.value) return null;
  return isCPFValid(control.value) ?
    null : { 'invalidCnpj': 'invalid cpf' };
}
function isCPFValid(cpf) {
  const regex = /[.-\s]/g;
  let strCPF = cpf.replace(regex, '');
  let Soma = 0;
  let Resto;
  for (let i = 1; i <= 9; i++) {
    Soma = Soma + parseInt(strCPF.substring(i - 1, i)) * (11 - i);
    Resto = (Soma * 10) % 11;
  }
  if ((Resto === 10) || (Resto === 11))
    Resto = 0;
  if (Resto !== parseInt(strCPF.substring(9, 10)))
    return false;
  Soma = 0;
  for (let i = 1; i <= 10; i++) {
    Soma = Soma + parseInt(strCPF.substring(i - 1, i)) * (12 - i);
    Resto = (Soma * 10) % 11;
  }
  if ((Resto === 10) || (Resto === 11))
    Resto = 0;
  if (Resto !== parseInt(strCPF.substring(10, 11)))
    return false;
  return true;
}

And this is how you’d use it in the Form Control’s declaration:

const formGroup = new FormGroup({
    'name': new FormControl('', [ Validators.required ]),
    'cpf': new FormControl('',
        [ Validators.required, CPFValidator ]
    )
})

Angular Reactive Forms are very powerful and its Docs are very complete. Even though sometimes you have to dig a little deep to find exactly what you want, you can almost certainly find it there with some examples.

Hope it helps 😉

References:

https://angular.io/guide/reactive-forms

#angular

Understanding Angular Reactive Forms
63.35 GEEK