Understanding Angular Reactive Forms

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.

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': '[email protected]',
  '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('[email protected]');

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

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Install Angular - Angular Environment Setup Process

Install Angular in easy step by step process. Firstly Install Node.js & npm, then Install Angular CLI, Create workspace and Deploy your App.

Basics of Angular: Part-1

What is Angular? What it does? How we implement it in a project? So, here are some basics of angular to let you learn more about angular. Angular is a Typesc

Angular Sass: How To Use Sass In Angular 9 Tutorial

Sass in Angular is an extension of CSS that allows you to use things like variables, nested rules, inline imports. Angular supports Sass, CSS, and Less.

How to Upgrade Angular 8 To Angular 9

Angular is currently the most popular JavaScript framework and is used by several expert developers when developing single-page applications or powerful web apps. It has become quite crucial for the developers of Angular to add more features to the framework and fix the bugs . Let's learn how to upgrade Angular 8 to Angular 9 using CLI.

Visualizing data with NGX-Charts in Angular

Data Science, Data Analytics, Big Data, these are the buzz words of today's world. A huge amount of data is being generated and analyzed every day. So communica