Better Programming

Advice for programmers.

Follow publication

Angular

Intro to Angular Reactive Forms

Lo Zarantonello
Better Programming
Published in
5 min readJul 14, 2022

--

Photo by Parrish Freeman on Unsplash

Angular offers two main approaches to handling user input through forms: reactive and template-driven. Both approaches build on top of a common Forms API.

In this post, I will build a form following the reactive approach, also known as model-driven forms.

Simple reactive forms with validators
Simple reactive forms with validators

I wrote an Intro To Angular Template-driven Forms, where I built the same form using the template-driven approach.

I found it useful to build the same form using the two approaches to appreciate the differences better.

Overview of Reactive Approach

According to the documentation, reactive forms “Provide direct, explicit access to the underlying forms object model.

If forms are a key part of your application, or you’re already using reactive patterns for building your application, use reactive forms.”

Furthermore, they add that reactive forms are more robust than template-driven forms: they’re more scalable, reusable, and testable.

Don’t take this as written in stone

The debate over the best approach may never be resolved.

Do you prefer template-driven forms or reactive forms?

In The Angular Plus Show podcast, they gave space to Ward Bell, a Google developer expert in Angular and president/co-founder at IdeaBlade.

Ward Bell has been using template-driven forms for years and is one of the best specialists on the topic.

Make sure you listen to the episode to form (lol) your opinion.

FormsModule and Two Directives

First of all, we need to remember to import ReactiveFormsModule because it “exports the required infrastructure and directives for reactive forms.”

Therefore, we import the ReactiveFormsModule in app.module.ts.

import { ReactiveFormsModule } from '@angular/forms';

and declare it in the imports in @NgModule.

imports: [BrowserModule, FormsModule, ReactiveFormsModule],

Note that FormsModule is there because the same application uses a template-driven form in another component.

If we require both, we should import them both.

However, FormsModule is important because both NgModel and NgForm directives are exported from FormsModule.

Building a Form Element

In theory, we could start from either the class or the template.

However, “Reactive forms provide a model-driven approach to handling form inputs whose values change over time.”

In reactive forms, it is more natural to start from the class, even though some people may prefer to start from the template, and that is totally fine.

Since Intro To Angular Template-Driven Forms, I started with a generic form element; I will follow that approach to show that some things are the same.

A Generic Form Element

A generic form element in Angular forms may look like the following:

<div>
<label for="email">Email</label>
<input type="email" id="email" [formControl]="email" />
</div>

Once again, this is pretty much plain HTML, except for [formControl]=”email".

The formControl binding comes from the FormControlDirective, which comes from the ReactiveFormsModule that we imported above.

If you are familiar with the Angular syntax, this isn’t new as it looks similar to property binding.

What does it bind to?

It binds to the email property in the class.

After importing FormControl, we can assign a new FormControl instance to email. FormControl “Tracks the value and validation status of an individual form control,” angular.io.

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
...
export class ReactiveFormComponent {
email = new FormControl('');
}

In this case, by using new FormControl('') we set the initial value of emailto an empty string.

Thanks to FormControl, we can listen for, update, and validate the state of the form element.

You get all the benefits of two-way binding and more, like validations.

We will get to validations soon.

Display FormControl values

You can easily display the value by using interpolation and the value property in the template as follows:

<p>Value: {{ email.value }}</p>

A second way is “Through the valueChanges observable where you can listen for changes in the form’s value in the template using AsyncPipe or in the component class using the subscribe() method”, angular.io.

From One Element to a Form

Starting from the generic element above, we can create the following form:

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
...
reactiveForm = new FormGroup({
name: new FormControl(''),
email: new FormControl(''),
age: new FormControl(''),
});

Note that we must import FormGroup from @angular/forms in every component where we want to create a new FormGroup instance.

We created a FormGroup instance in the class. FormGroup “Tracks the value and validity state of a group of FormControl instances,” angular.io.

We then need to associate the FormGroup model and view inside the template using property binding.

As for template-driven forms, we want to have a way to work with the form as a whole rather than dealing with each element.

Initial Reactive Form

First difference

We can see the first difference with template-driven forms in the formtag. We are not using a reference variable anymore.

Second difference

A second difference consists of formControlName.

“The formControlName input provided by the FormControlName directive binds each individual input to the form control defined in FormGroup,” angular.io.

However, the form group instance provides the source of truth for the model value.

Third difference

A third difference is that we don’t need to use the name attribute in the input tags.

Your app wouldn’t crash, but you would get an ugly-looking error in your console.

error error: if ngModel is used within a form tag, either the name attribute must be set
The error in the console when we forget the name attribute in template-driven forms.

As a side note, you can also group controls into a single form by using a Form Array. We won’t discuss this in this post.

Validation

At the moment, we have no validation.

Reactive forms handle validation through special functions called validators. Angular provides built-in validators that you can use off the shelf.

The easiest way to use validators is by passing them as the second parameter to FormControl.

reactiveForm = new FormGroup({
name: new FormControl('', Validators.required),
email: new FormControl(''),
age: new FormControl('', [
Validators.required,
Validators.max(99),
Validators.min(18),
]),
});

In the code snippet, I added two validators. The first one makes name required. The second validator check that age is between 18 and 99.

By updating the submit button as follows:

<button type="submit" [disabled]="reactiveForm.invalid">Submit</button>

we make sure that the button is active only when the form is valid, e.g., the conditions in the validators are met.

When the built-in validators aren’t enough, we can even create custom validators for more complex cases.

Quick Summary

Angular offers two main approaches to building forms: reactive and template-driven. In this post, we explored the reactive approach.

Both approaches build on top of a common Forms API.

  1. Import ReactiveFormsModule in app.module.ts
  2. Use new FormControl() to instantiate a form control
  3. Use new FormGroup() to create a group of form controls
  4. Bind the FormGroup model in the class with the view through property binding [FormGroup]="myFormGroupName"
  5. The <form> tag implement NgForm by default after importing ReactiveFormsModule

Feel free to take a look at the code on GitHub.

--

--

Lo Zarantonello
Lo Zarantonello

Written by Lo Zarantonello

Software Engineer | Instructor | Life Form Exploring His Surroundings

No responses yet

Write a response