Handling Forms And Validation In Angular
Published: 2023-11-21 09:54:51

 by [Taminoturoko Briggs](https://blog.openreplay.com/authors/taminoturoko-briggs)


<blockquote><em>
Forms are a vital component of many applications today, which allows users to perform tasks like authentication, payment, feedback, etc. Hence, handling forms and implementing proper validation becomes crucial to delivering a seamless user experience. Angular provides two approaches for working with forms, reactive and template-driven, and this article will show you them, their use cases, and how to implement validation proper data validation.
</em></blockquote>


<div style="background-color:#efefef; border-radius:8px; padding:10px; display:block;">
<hr/>
<h3><em>Session Replay for Developers</em></h3> 
<p><em>Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data.</em></p>
<img alt="OpenReplay" style="margin-top:5px; margin-bottom:5px;" width="768" height="400" src="https://raw.githubusercontent.com/openreplay/openreplay/main/static/openreplay-git-hero.svg" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<p><em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em><p>
<hr/>
</div>


## Forms in Angular

Angular provides two ways of working with forms:

- Reactive forms are a flexible way to manage and validate forms within your application. They provide direct, explicit access to the underlying form's object model, which represents a form and its elements as objects within the application's component code. They are more reusable, scalable, and testable and are a good option if your application heavily relies on forms.

- Template-driven forms are an approach to handling forms within your application by using the template to define the form structure and behavior. They rely on directives in the template to create and manipulate the form's object model. Compared to reactive forms, template-driven forms are more straightforward to add and recommended if you're building a basic form. 

Both reactive forms and template-driven forms have similar building blocks. However, they differ in how these building blocks are used and implemented.

Here's a list of their common building blocks:

* [FormControl](https://angular.io/api/forms/FormControl): This class represents an individual form control, such as an input field. It allows you to track the value and validation status of the control.

* [FormGroup](https://angular.io/api/forms/FormGroup): This class is used to manage and track the values of a collection of form controls.

* [FormArray](https://angular.io/api/forms/FormArray): This class Is similar to `FormGroup` but is designed explicitly for dynamically managing an array of controls.


## Setting up Angular
Let's start by creating a new Angular app and the form used in both the template-driven and reactive form sections to demonstrate how they work. 

To generate a new Angular app, enter the following commands in your terminal:
```shell
ng new angular-forms
```
When prompted, pick CSS for styling, and don't add Angular routing. The above will create an angular app called `angular-forms`. 
After the installation is complete, we can start the app with `npm start`.

Next, let's create the form. In the terminal, cd into the created project and enter the following command to create a form component.
```shell
ng g c profile-form
```
Navigate to `src/form/profile-form.compnent.html` and add the following lines of code:
```html
<div class="profile-form">
  <h2>Profile Form</h2>
  <form name="profile-form">
    <div>
      <label for="full-name">Full name</label><br />
      <input type="text" id="full-name" placeholder="Enter your full name"/>
    </div>
    <div>
      <label for="username">Username</label><br />
      <input type="text" id="username" placeholder="Enter your username"/>
    </div>
    <div>
      <label for="birth">Date of Birth</label><br />
      <input type="date" id="birth"/>
    </div>
    <button>Done</button>
  </form>
</div>

<style>
  .profile-form {
    max-width: 400px;
    box-shadow: 0 4px 4px rgb(179, 184, 189);
    padding: 50px 20px;
    margin: 0 auto;
    margin-top: 100px;
  }

  .profile-form h2 {
    font-family: monospace;
    font-size: 24px;
    text-align: center;
  }

  .profile-form form {
    display: flex;
    flex-direction: column;
    gap: 20px;
  }

  .profile-form input {
    border: 1px solid rgb(179, 184, 189);
    height: 50px;
    width: 100%;
    font-size: 16px;
    margin-top: 5px;
    padding: 0 10px;
    box-sizing: border-box;
  }
  
  .profile-form input:focus {
    outline: none;
  }

  .profile-form button {
    background-color: rgb(0, 119, 255);
    padding: 15px 25px;
    width: fit-content;
    color: white;
    border: none;
    border-radius: 4px;
    margin-left: auto;
  }
</style>
```
Next, head over `src/app.component.html` and replace the entire content with the following:
```html
<app-profile-form></app-profile-form>
```
With this, when we open http://localhost:4200/ in the browser, we will see the following:

![-](https://blog.openreplay.com/images/handling-forms-and-validation-in-angular/images/image01.png)


## Handling form and validation in template-driven form
The first step to build a template-driven form is to import the [FormsModule](https://angular.io/api/forms/FormsModule) into our app, which includes directives to enable us to build a template-driven form. The two most important ones are:

* [NgForm](https://angular.io/api/forms/NgForm): Creates a top-level FormGroup instance and binds it to a `<form>` to keep track of all form control values of a particular form and validation status. This directive is automatically added to all form tags once the `FormsModule` is imported.
    
* [NgModel](https://angular.io/api/forms/NgModel): Creates a `FormControl` instance and binds it to a form control element to track its value, user interaction, and validation status. It can be used for one-way data binding to supply the value of a property in the component to the form control without updating the property when the form control value changes using `[ngModel]` or two-way data binding, which keeps the properties/data-model in sync with the form control values.

Head over to `src/app.module.ts` and add the following import: 
```javascript
import { FormsModule } from '@angular/forms';
```
Next, include it in the imports property of `@NgModules`: 
```javascript
imports: [
  ...
  FormsModule,
]
```
The `NgForm` directive will be attached to our profile form. To get access to its [properties](https://angular.io/api/forms/NgForm#properties) which show the form status, we can define a template variable that has a reference to the `ngForm` directive instance like this:
```html
<form #profileForm="ngForm" name="profile-form"> 
```
### Accessing and working with form control values
To access the form control values, we need to add the `ngModel` directive to the input tags.
    
Depending on the use case, `ngModel` can be added without a binding or one-way or two-way binding.
    
When using `ngModel` within a `<form>` tag, it is often unnecessary to use any binding since the `ngForm` instance of the form holds the current values of the registered form controls, which can be accessed using a template variable. For submission, the variable can be passed to the submit function. 
    
A use case to use the binding syntax within a `<form>` tag is when initial values must be passed to the controls. For this, the one-way binding will suffice.
    
Lastly, for two-way binding, as you might have deduced, it's most suitable to use when the controls are not with a `<form>` tag. 

Let's take a look at some examples of the above use cases.
To use `ngModel` within a `<form>` tag, add it to each form control along with a name attribute, which will be used to register the control with the parent form's `NgForm` instance. 
```html
<form #profileForm="ngForm" name="profile-form">
  <div>
    <label for="full-name">Full name</label><br />
    <input
      type="text"
      id="full-name"
      ngModel
      name="fullname"
      placeholder="Enter your full name "
    />
  </div>
  <div>
    <label for="username">Username</label><br />
    <input
      type="text"
      id="username"
      ngModel
      name="username"
      placeholder="Enter your username"
    />
  </div>
  <div>
    <label for="birth">Date of Birth</label><br />
    <input type="date" id="birth" ngModel name="birth" />
  </div>
  <button>Done</button>
</form>
```     
On form submission, the `profileForm` can be passed to a submit function like this:
```html
<form
  #profileForm="ngForm"
  name="profile-form"
  (ngSubmit)="onSubmit(profileForm)"
></form>
```
To pass initial values to the controls using one-way binding, first, in the component, you can define a data model that holds the initial values for the controls like this:
```javascript
model = {
  fullname: 'John doe',
  username: 'john',
  date: '2023-01-11'
}
```
Component properties can also be used.

Next, in the template, the model's properties will be assigned to `ngModel`.
```html
<div>
  <label for="full-name">Full name</label><br />
  <input
    type="text"
    id="full-name"
    #name="ngModel"
    [ngModel]="model.fullname"
    name="fullname"
    placeholder="Enter your full name "
  />
</div>
<div>
  <label for="username">Username</label><br />
  <input
    type="text"
    id="username"
    [ngModel]="model.username"
    name="username"
    placeholder="Enter your username"
  />
</div>
<div>
  <label for="birth">Date of Birth</label><br />
  <input type="date" id="birth" [ngModel]="model.date" name="birth" />
</div>
```
On submission, rather than using the model's values, the template variable is passed to the submit function, as done above. 
    
Then, for two-way binding. First, you will define the data model in the component:
```javascript
model = {
  fullname: '',
  username: '',
  date: ''
}
```  
Next, assign it to `ngModel` in the template like this: 
```html
<div>
  <label for="full-name">Full name</label><br />
  <input
    type="text"
    id="full-name"
    [(ngModel)]="model.fullname"
    name="fullname"
    placeholder="Enter your full name "
  />
</div>
<div>
  <label for="username">Username</label><br />
  <input
    type="text"
    id="username"
    [(ngModel)]="model.username"
    name="username"
    placeholder="Enter your username"
  />
</div>
<div>
  <label for="birth">Date of Birth</label><br />
  <input type="date" id="birth" [(ngModel)]="model.date" name="birth" />
</div>
``` 
On submission, the model's values can be used since it will always be in sync with the view.
    
Although these use cases determine how best to use `ngModel`, it is not tied to a particular use case.
    
For our use case where the controls are within a form, we will use `ngModel` without a binding.

### Validation in template-driven form
Validation in template-driven form is relatively straightforward. To add validation, we can use the [HTML form validation attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation#validation-related_attributes), which we are well familiar with. Angular also allows us to create [custom validators](https://angular.io/guide/form-validation#adding-custom-validators-to-template-driven-forms) if the built-in validators don't suffice for our use case.

Now, let's add validation to our form and disable the submit button if the form is not valid. Head over to the `profile-form.component.html` and modify the form to the following: 
```html
<form
  #profileForm="ngForm"
  name="profile-form"
  (ngSubmit)="onSubmit(profileForm)"
>
  <div>
    <label for="full-name">Full name</label><br />
    <input
      type="text"
      required
      minlength="5"
      id="full-name"
      ngModel
      name="fullname"
      placeholder="Enter your full name "
    />
  </div>
  <div>
    <label for="username">Username</label><br />
    <input
      type="text"
      id="username"
      required
      ngModel
      name="username"
      placeholder="Enter your username"
    />
  </div>
  <div>
    <label for="birth">Date of Birth</label><br />
    <input type="date" id="birth" required ngModel name="birth" />
  </div>
  <button [disabled]="!profileForm.form.valid">Done</button>
</form>
``` 
Above, we have declared all fields as required and specified a `minLenght` of five(5) for the `fullname` field. We are disabling the button if the form is not valid using the `valid` property in the `ngForm` instance. 

We have also specified a submit function, which will be called when the form is submitted. To create this function, in `profile-form.component.ts`, add the following import: 
```javascript
import { NgForm } from '@angular/forms';
```
Next, add the following lines of code:
```javascript
onSubmit(profileForm: NgForm) {
  console.log(
    `
      Full Name is ${profileForm.form.controls?.['fullname'].value}
      Username is ${profileForm.form.controls?.['username'].value}
      Date of birth is ${profileForm.form.controls?.['birth'].value}
    `
  )
}
```
Although the form has validated, there are no visual cues to inform users about the state of the form, which negatively impacts the user experience. For that, we can create template variables that reference `ngModel` for all controls and use the variables to conditionally add a class name, which will be used for styling, or better still, we can use the class name Angular applies based on the control's state. Below is a list describing these class names:
    
* `.ng-valid`: Applied when the control is valid.
* `.ng-invalid`: Applied when the control is not valid.
* `.ng-pristine`: Applied when the control's value has not changed. 
* `.ng-dirty`: Applied when the control's value has changed.
* `.ng-untouched`: Applied when the control has been visited.
* `.ng-touched`: Applied when the control has not been visited.
* `.ng-submitted` (form element only): Applied when the form is submitted.
    
We can inspect the `<input>` elements from the browser developer tools to see how these classes are added and removed.
    
Now, using these classes, let's display appropriate errors and change the button's color when the form is invalid.

In `profile-form.component.html`, modify the form to the following:
```html
<form
  #profileForm="ngForm"
  name="profile-form"
  (ngSubmit)="onSubmit(profileForm)"
>
  <div>
    <label for="full-name">Full name</label><br />
    <input
      type="text"
      required
      minlength="5"
      id="full-name"
      #fullname="ngModel"
      ngModel
      name="fullname"
      placeholder="Enter your full name "
    />
    <div *ngIf="fullname.invalid && fullname.dirty" class="error">
      <p *ngIf="fullname.errors?.['required']">Fullname is required.</p>
      <p *ngIf="fullname.errors?.['minlength']">
        Fullname must be at least 5 characters long.
      </p>
    </div>
  </div>
  <div>
    <label for="username">Username</label><br />
    <input
      type="text"
      id="username"
      required
      #username="ngModel"
      ngModel
      name="username"
      placeholder="Enter your username"
    />
    <p *ngIf="username.errors?.['required'] && username.dirty" class="error">
      Username is required.
    </p>
  </div>
  <div>
    <label for="birth">Date of Birth</label><br />
    <input
      type="date"
      id="birth"
      required
      #birth="ngModel"
      ngModel
      name="birth"
      placeholder="date of birth"
    />
    <p *ngIf="birth.errors?.['required'] && birth.dirty" class="error">
      Date of birth is required.
    </p>
  </div>
  <button [disabled]="!profileForm.form.valid">Done</button>
</form>
``` 
Above, we have created variables that reference the `ngModel` instance for each control and then use the variables to display the appropriate error messages.
    
The `ngModel` instance contains [properties](https://angular.io/api/forms/NgModel#inherited-from-abstractcontroldirective) that indicate the state of the control. Above, we have used these properties to show errors conditionally.
    
Next, add the following styling in the `<style></style>` tag, which uses the defined `.error` class and applied classes to style the error message, controls, and button.
```css
.profile-form .ng-invalid:not(form).ng-dirty {
  border: 2px solid rgb(235, 104, 104);
}

.ng-invalid button {
  background-color: gainsboro;
}

.error {
  color: rgb(235, 104, 104);
}
```
With this, when the form or its control (e.g., `fullname`) is invalid, it will look like the following:

![-](https://blog.openreplay.com/images/handling-forms-and-validation-in-angular/images/image02.png)

### Validation with custom validators in template-driven form
At times, the built-in validators will not suffice for our use case. For that, Angular allows us to define our own validators.

In template-driven forms, custom validators are created using directives that will be added to the controls. The directive must implement the [Validator](https://angular.io/api/forms/Validator) interface, which contains a `validate` function that will be called when the directive is invoked.

Here is what the Validator interface looks like:
```javascript
interface Validator {
  validate(control: AbstractControl<any, any>): ValidationErrors | null
  registerOnValidatorChange(fn: () => void)?: void
}
```
The `validate` function takes in an Angular control object and returns either `null` when the control value is valid or an error object when it isn't. 

As an example, let's create a custom validator that checks if a user is up to 18 years old. 

In `app/profileForm`, create a `notOfAge.directive.ts` file and add the following.
```javascript
import { Directive } from "@angular/core";
import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator } from "@angular/forms";

function notOfAgeValidator (control: AbstractControl): ValidationErrors | null {
  const acceptedMinAge = 18
  const dateOfBirth = new Date(control.value)
  const age = new Date().getFullYear() - dateOfBirth.getFullYear()

  return age < acceptedMinAge ? {notOfAge: true, minAge: 18} : null
}

@Directive({
  selector: '[appNotOfAge]',
  providers: [{provide: NG_VALIDATORS, useExisting: NotOfAgeDirective, multi: true}]
})
export class NotOfAgeDirective implements Validator {
  
  validate(control: AbstractControl): ValidationErrors | null {
    return notOfAgeValidator(control)
  }
}
```
Above, the `validate` method returns the `notOfAgeValidator` validation function, which does the age check and returns an error object if the age is less than 18 and null if it isn't.

Angular recognizes this as a validation directive due to the presence of the [NG_VALIDATORS](https://angular.io/api/forms/NG_VALIDATORS) provider, which is a predefined provider with an extensible collection of validators. We have registered this provider in the Angular provider metadata and set `multi` to `true` since there can be more validation directives.

To use the custom validator, we need first to declare it in the app module and then add its selector `appNotOfAge` to the date of birth control. 

Head over to `app/app.module.ts`, import the directive, and add it to the `declarations` array of `@NgModule`:
```javascript
import { NotOfAgeDirective } from './profile-form/notOfAge.directive';

declarations: [
  ...
  NotOfAgeDirective,
],
```
Next, head over to `app/profile-form/profile-form.component` and add the directive's selector to the date of birth control:
```html
<input type="date" id="birth" required #birth="ngModel" appNotOfAge ngModel name="birth" />
```
Then we can display an error message as we do with the built-in validators like this:
```html
<p *ngIf="birth.errors?.['notOfAge'] && birth.dirty" class="error">
  You are not of age. You must be at least  {{birth.errors?.['minAge']}}  years old.
</p>
```
With this, when the age is not valid, it will be indicated in the form.

![-](https://blog.openreplay.com/images/handling-forms-and-validation-in-angular/images/image03.png)



Custom validators are also used for cross-field validation, which involves checking the validity of a control's value by using other controls. A typical example of this is the password confirmation validation, which we will be implementing.

To implement the password confirmation cross-field validation, first, add two input fields of `type=password` to our form like this:
```html
<div>
  <label for="password">Password</label><br />
  <input
    type="password"
    id="password"
    required
    #password="ngModel"
    ngModel
    name="password"
    placeholder="Enter your password"
  />
</div>
<div>
  <label for="confirm-password">Confrim password</label><br />
  <input
    type="password"
    id="confirm-password"
    required
    #confirmPassword="ngModel"
    ngModel
    name="confirm-password"
    placeholder="Confirm your password"
  />
</div>
```
Next, implement the validator directive like this:
```javascript
import { Directive } from "@angular/core";
import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator } from "@angular/forms";

function confirmPasswordValidator (control: AbstractControl): ValidationErrors | null {
 
  const password1 = control.get('password')
  const confirmPassword = control.get('confirm-password');

  return confirmPassword !== password1 ? {passwordNotSame: true} : null
}

@Directive({
  selector: '[appConfirmPassword]',
  providers: [{provide: NG_VALIDATORS, useExisting: ConfirmPasswordDirective, multi: true}]
})
export class ConfirmPasswordDirective implements Validator {
  validate(control: AbstractControl): ValidationErrors | null {
    return confirmPasswordValidator(control)
  }
}
```
Unlike regular custom validators, the directive must be registered at the highest level in the form, which is the `form` tag for cross-field validation. This is why, in the `confirmPasswordValidator` validation function above, we are retrieving the controls using the `get()` method.

Now that the directive has been created, declare it in the app module next. 
```javascript
import { ConfirmPasswordDirective } from './profile-form/confirmPassword.directive';


declarations: [
  ...
  ConfirmPasswordDirective,
],
```
Then add it to the form tag like this:
```html
<form
  #profileForm="ngForm"
  name="profile-form"
  (ngSubmit)="onSubmit(profileForm)"
  appConfirmPassword
></form>
```
Finally, we can display an error message using the following code:
```html
<p
  *ngIf="profileForm.form.errors?.['passwordNotSame'] && confirmPassword.dirty"
  class="error"
>
  Password does not match.
</p>
```
Although the password confirmation error message will display, there will be no red border around its input field. This is because in our style we are using the `ng-invalid` class, which is present in each control when it's invalid to add the red border. Still, for cross-field validation, when the validated control is invalid, the `ng-invalid` class will only be present `form` tag.

To fix this, we can conditionally add a class when the password does not match, which will be used to add the red border. To do this, modify the `confirm-password` control to the following:
```html
<input 
  type="password" 
  id="confirm-password" 
  required 
  #confirmPassword="ngModel" 
  ngModel 
  name="confirm-password" 
  placeholder="Confirm your password"
  [ngClass]="{'input-error': profileForm.form.errors?.['passwordNotSame'] && confirmPassword.dirty}"
/>
```
Next, add the following styling:
```css
.profile-form .input-error {
  border: 2px solid rgb(235, 104, 104); 
}
```
With this, when the passwords do not match, it will be indicated in the form like this:

![-](https://blog.openreplay.com/images/handling-forms-and-validation-in-angular/images/image04.png)



### Multi-step template-driven form with validation
When a form becomes lengthy and complex, it becomes intimidating to users. A well-known UI design technique used to handle this is a multi-step form. In this section, we will learn how to implement a multi-step form with validation when using template-driven forms.

We will break down the profile form into two steps to demonstrate how to implement a multi-step form: `NAME` and `BIRTH`. `NAME` will contain the `fullname` and `username` control, and `BIRTH` will contain the date of birth control. For this tutorial, we will implement a basic multi-step form with no progress headers. If you want a more complex multi-step form, check out the [Angular Material Stepper component](https://material.angular.io/components/stepper/overview).

Implementing a multi-step form is quite straightforward. We need to conditionally render the steps of a form based on the value of a numeric variable, which will be decreased or increased by one when the previous or next button is clicked. 

To get started, In the `profile-form.component.ts` file, add the following lines of code before the `onSubmit()` function in the class:
```javascript
step = 1;

previousStep () {
  if (this.step !== 1) this.step -= 1 
}

nextStep () {
  if(this.step !== 2) this.step += 1
}

stepTitle () {
  switch (this.step){
    case 1: return 'NAME'
    case 2: return 'BIRTH'
    default: return ''
  }
}
```
In the above code, we have defined a `step` property that will be used to conditionally render each step in the form, and two methods: `previousStep` and `nextStep` to decrease or increase the step by one (1) respectively, and a `stepTitle` method which returns the title of each step.

Next, modify the `<div>` tag with the class name of `profile-form` in `profile-form.component.html` to the following:
```html
<div class="profile-form">
  <h2>{{ stepTitles() }}</h2>
  <form
    #profileForm="ngForm"
    name="profile-form"
    (ngSubmit)="onSubmit(profileForm)"
  >
    <div [hidden]="step !== 1">
      <div>
        <label for="full-name">Full name</label><br />
        <input
          type="text"
          required
          minlength="5"
          id="full-name"
          #fullname="ngModel"
          ngModel
          name="fullname"
          placeholder="Enter your full name "
        />
        <div *ngIf="fullname.invalid && fullname.dirty" class="error">
          <p *ngIf="fullname.errors?.['required']">Fullname is required.</p>
          <p *ngIf="fullname.errors?.['minlength']">
            Fullname must be at least 5 characters long.
          </p>
        </div>
      </div>
      <div>
        <label for="username">Username</label><br />
        <input
          type="text"
          id="username"
          required
          #username="ngModel"
          ngModel
          name="username"
          placeholder="Enter your username"
        />
        <p
          *ngIf="username.errors?.['required'] && username.dirty"
          class="error"
        >
          Username is required.
        </p>
      </div>
    </div>
    <div [hidden]="step !== 2">
      <label for="birth">Date of Birth</label><br />
      <input
        type="date"
        id="birth"
        required
        #birth="ngModel"
        ngModel
        name="birth"
        placeholder="date of birth"
      />
      <p *ngIf="birth.errors?.['required'] && birth.dirty" class="error">
        Date of birth is required.
      </p>
    </div>

    <div class="buttons">
      <button type="button" (click)="previousStep()">Prev</button>
      <button type="button" (click)="nextStep()">Next</button>
      <button *ngIf="step === 2" [disabled]="!profileForm.form.valid">
        Done
      </button>
    </div>
  </form>
</div>
```
In the above code, we called `stepTitle()` to show the title of the current step, hide or show the controls of each step based on the value of the `step` property, add a Previous and Next button that calls the corresponding method when clicked, and show the submit button only at the ending of the form.

You might be wondering why we are using the `hidden` property to conditionally hide the controls rather than `*ngIf`. We do this so that we are still able to access the control values from the `ngForms` instance. When `*ngIf` is used, the controls are not added to the DOM, so its value cannot be accessed, but this is not the same when using `[hidden]`, which includes the controls in the DOM but doesn't display them.

At the bottom of the page, modify the `<style>` tag to the following to properly style the form:
```css
<style>
  .profile-form {
    max-width: 400px;
    box-shadow: 0 4px 4px rgb(179, 184, 189);
    padding: 50px 20px;
    margin: 0 auto;
    margin-top: 100px;
  }

  .profile-form h2 {
    font-family: monospace;
    font-size: 24px;
    text-align: center;
  }

  .profile-form form > div:not([hidden]):first-child {
    display: flex;
    flex-direction: column;
    gap: 20px;
  }

  .profile-form input {
    border: 1px solid rgb(179, 184, 189);
    height: 50px;
    width: 100%;
    font-size: 16px;
    margin-top: 5px;
    padding: 0 10px;
    box-sizing: border-box;
  }

  .profile-form input:focus {
    outline: none;
  }

  .profile-form button {
    background-color: rgb(0, 119, 255);
    padding: 15px 25px;
    width: fit-content;
    color: white;
    border: none;
    border-radius: 4px;
  }

  .profile-form .ng-invalid:not(form).ng-dirty {
    border: 2px solid rgb(235, 104, 104); 
  }

  .error {
    color:rgb(235, 104, 104)
  }

  .buttons {
    margin-top: 20px;
    display: flex;
    gap: 10px;
    justify-content: right;
  }
</style> 
```
With this, the form will look like the following:

![-](https://blog.openreplay.com/images/handling-forms-and-validation-in-angular/images/image05.gif)


Although we have implemented the multi-form, even when the fields are empty, we can still click the next button, and there are no visual cues to indicate when a button cannot be clicked.

To fix this, we will add a `disabled` attribute to all the buttons and set it to false or true based on whether a particular step is valid. When the `disabled` attribute is true, it is added to the element; when it is false, it is not added. So, we will use the presence of the disabled property to change the color of the button to gray to indicate that it can't be clicked.

First, in `profile-form.component.ts`, add the following method before the `onSubmit` method:
```javascript
stepIsValid (profileForm: NgForm) {
  if(this.step === 1) {
    return (
      !!profileForm.form.controls?.['fullname'].value 
      && !!profileForm.form.controls?.['username'].value
      && !profileForm.form.controls?.['fullname'].errors
      && !profileForm.form.controls?.['username'].errors
    )
  }else {
    return (
      !!profileForm.form.controls?.['birth'].value 
      && !profileForm.form.controls?.['birth'].errors
    )
  }
}
```
This method returns true if the controls in a step are not empty and have no errors; if not, it returns false. 

Next, in `profile-form.component.html`, modify the previous and next buttons to the following:
```html
<button type="button" [disabled]="step === 1" (click)="previousStep()">
  Prev
</button>
<button
  type="button"
  [disabled]="!stepIsValid(profileForm)"
  (click)="nextStep()"
>
  Next
</button>
```
In the above code, we disable the previous button if the form is in the first step and the next button using the `stepIsValid()` method returns false.

Add the following styling to the `<style>` tag.
```css
.profile-form button[disabled] {
  background-color: gainsboro;
}
```
With this, the form will look like the following:

![-](https://blog.openreplay.com/images/handling-forms-and-validation-in-angular/images/image06.gif)


<CTA_Middle_Frameworks />



## Handling form and validation in reactive form
In this section for the form, we will be using the profile form created in the `Setting up Angular` section of this article.

To build a reactive form, the first step is to import [ReactiveFormsModule](https://angular.io/api/forms/ReactiveFormsModule#directives), which provides the [directives](https://angular.io/api/forms/ReactiveFormsModule#directives) for reactive forms. Then, add it to the NgModules imports property.

Head over to `src/app.module.ts` and add the following import:
```javascript
import { ReactiveFormsModule } from '@angular/forms';
```
Next, include it in the imports property of `@NgModules`:
```javascript
imports: [
  ...
  ReactiveFormsModule,
]
```
As mentioned earlier, with reactive forms, the form model is explicitly defined in the component rather than created by a directive like in template-driven form. After the form model is defined, it can be linked to specific form elements in the view. 

Here is an example where a `FormControl` instance is created passed an empty string as the initial values and linked to a form control element in the template.
```javascript
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-profile-form',
  template: `
    <input type="text"  [formControl]="username">
  `
})
export class ProfileFormComponent {
  username = new FormControl('');
}
```
The `[formControl]` directive in the above code is used to link a standalone FormControl instance to a form control element.
It is provided by `ReactiveFormsModule`. 

Each form control instance provides properties and methods to access information about it and modify its state, which is of the underlying [AbstractControl](https://angular.io/api/forms/AbstractControl) class.

Using these properties, to display a form control value, we can use the `valueChanges` observable to listen for changes using [AsyncPipe](https://angular.io/api/common/AsyncPipe) in the template or using the subscribe method in the component. We can also use the `value` property. Here are examples.

Using the `valueChange` observer.
```javascript
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-profile-form',
  template: `
    <input type="text"  [formControl]="username">
    {{username.valueChanges | async}}
  `
})
export class ProfileFormComponent {
  username = new FormControl('');

  constructor() {
    this.username.valueChanges.subscribe((newValues) => {
      console.log('Username:', newValues);
    });
  }
}
```
Using the `value` property:
```javascript
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-profile-form',
  template: `
    <input type="text" [formControl]="username">
    <button (click)="onClick()">Click</button>
    {{username.value}}

  `
})
export class ProfileFormComponent {
  username = new FormControl('');

  onClick() {
    console.log(this.username.value)
  }
}
```
In most cases, more than one form control element is wrapped in a `<form>` or `<div>` tag. For that, reactive forms provide the `FormGroup` and `FormArray` for creating the form model, each with a different use case, which was mentioned earlier. 

For our case, which involves converting the profile form to a reactive form, we will be using `FormGroup.`

Now, let's learn how to convert the profile form to a reactive form and add a submit function that logs the values of the controls.

First, define the form model, which contains form controls instance corresponding to the input fields in the template, and add the submit function in the `profile-form.component.ts` file:
```javascript
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-profile-form',
  templateUrl: './profile-form.component.html',
  styleUrls: ['./profile-form.component.css']
})
export class ProfileFormComponent {
  
  profileForm = new FormGroup({
    fullname: new FormControl(''),
    username: new FormControl(''),
    dataOfBirth: new FormControl('')
  });

 
  onSubmit() {
    console.log(
      `
        Full Name is ${this.profileForm.value.fullname}
        Username is ${this.profileForm.value.username}
        Date of birth is ${this.profileForm.value.dataOfBirth}
      `
    )
  }
}
```
Next, in `profile-form.component.html` modify the `<form>` tag to the following:
```html
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()" name="profile-form">
  <div>
    <label for="full-name">Full name</label><br />
    <input
      type="text"
      id="full-name"
      formControlName="fullname"
      placeholder="Enter your full name"
    />
  </div>
  <div>
    <label for="username">Username</label><br />
    <input
      type="text"
      id="username"
      formControlName="username"
      placeholder="Enter your username"
    />
  </div>
  <div>
    <label for="birth">Date of Birth</label><br />
    <input
      type="date"
      id="birth"
      formControlName="dataOfBirth"
      placeholder="date of birth"
    />
  </div>
  <button>Done</button>
</form>
```
### Validation in reactive forms
In Reactive forms, validation is done using validation functions directly added to the form control model in the component class. These functions are called whenever the value of the control changes. 
To add validation, we can use the built-in validators which are the implementation of the [HTML form validation attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation#validation-related_attributes) from the [Validators](https://angular.io/api/forms/Validators) class or we can write [custom validators](https://angular.io/guide/form-validation#defining-custom-validators) if the built-in validators don't suffice for our use case.

Now, let's add validation to our profile form and disable the submit button if the form is not valid.

In `profile-form.component.ts`, import the Validators class and modify the form model to the following:
```javascript
import { Validators } from '@angular/forms';

profileForm = new FormGroup({
  fullname: new FormControl('', [
    Validators.required,
    Validators.minLength(5)
  ]),
  username: new FormControl('', Validators.required),
  dataOfBirth: new FormControl('', Validators.required)
});
```
Above using the Validators class, we have made all controls required and added a minimum length of 5 characters for the `fullname` control.

Next, in `profile-form.component.html`, modify the form to show errors when a control is invalid using the provided controls state properties and disable the submit button when the form is invalid:
```html
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()" name="profile-form">
  <div>
    <label for="full-name">Full name</label><br />
    <input
      type="text"
      id="full-name"
      formControlName="fullname"
      placeholder="Enter your full name"
    />
    <div
      *ngIf="
        profileForm.controls.fullname.invalid &&
        profileForm.controls.fullname.dirty
      "
      class="error"
    >
      <p *ngIf="profileForm.controls.fullname.errors?.['required']">
        Fullname is required.
      </p>
      <p *ngIf="profileForm.controls.fullname.errors?.['minlength']">
        Fullname must be at least 5 characters long.
      </p>
    </div>
  </div>
  <div>
    <label for="username">Username</label><br />
    <input
      type="text"
      id="username"
      formControlName="username"
      placeholder="Enter your username"
    />
    <p
      *ngIf="
        profileForm.controls.username.errors?.['required'] &&
        profileForm.controls.username.dirty
      "
      class="error"
    >
      Username is required.
    </p>
  </div>
  <div>
    <label for="birth">Date of Birth</label><br />
    <input
      type="date"
      id="birth"
      formControlName="dataOfBirth"
      placeholder="date of birth"
    />
    <p
      *ngIf="
        profileForm.controls.dataOfBirth.errors?.['required'] &&
        profileForm.controls.dataOfBirth.dirty
      "
      class="error"
    >
      Date of birth is required.
    </p>
  </div>
  <button [disabled]="profileForm.invalid">Done</button>
</form>
```
Now, to add visual cues to inform users about the state of the form, we will be using the class name Angular applies to the form and its controls based on their state, which includes:
    
* `.ng-valid`: Applied when the control is valid.
* `.ng-invalid`: Applied when the control is not valid.
* `.ng-pristine`: Applied when the control's value has not changed. 
* `.ng-dirty`: Applied when the control's value has changed.
* `.ng-untouched`: Applied when the control has been visited.
* `.ng-touched`: Applied when the control has not been visited.
* `.ng-submitted` (form element only): Applied when the form is submitted.
    
You can see how Angular adds and removes these classes from the browser developer tools.

Add the following styling to the `<style>` tag:
```css
.profile-form .ng-invalid:not(form).ng-dirty {
  border: 2px solid rgb(235, 104, 104);
}

.ng-invalid button {
  background-color: gainsboro;
}

.error {
  color: rgb(235, 104, 104);
}
```
With this, when the form or its control (e.g., `fullname`) is invalid, it will look like the following:

![-](https://blog.openreplay.com/images/handling-forms-and-validation-in-angular/images/image07.png)


### Validation with custom validators in reactive form
Creating a custom validator in reactive form is quite straightforward, unlike a template-driven form. 

To do this, we need to create a function that implements the [ValidatorFn](https://angular.io/api/forms/ValidatorFn), which is a function that receives a control parameter and synchronously returns either `null` (if there is no error) or a validation error object.

Here is what the `ValidatorFn` interface looks like:
```javascript
interface ValidatorFn {
  (control: AbstractControl<any, any>): ValidationErrors | null
}
```
As an example, let's create a custom validator to check if a user is up to 18 years old using the `dateOfBirth` control of the profile form. 

In `app/profileForm` create a `notOfAge.directive.ts` file and add the following lines of code:
```javascript
import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms"

export const notOfAgeValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
  const acceptedMinAge = 18
  const dateOfBirth = new Date(control.value)
  const age = new Date().getFullYear() - dateOfBirth.getFullYear()

  return age < acceptedMinAge ? {notOfAge: true, minAge: 18} : null
}
```
Next, in the `profile-form.component.ts` file, import `notOfAgeValidator` and add it to the `dataOfBirth` control like this:
```javascript
import { notOfAgeValidator } from './notOfAge.directive';
...

profileForm = new FormGroup({
  ...
  dataOfBirth: new FormControl('', [Validators.required, notOfAgeValidator])
});
```
That's it. With these few steps, when a date of birth is entered in the form, it will be validated based on the custom validator. Now, to display an error, we can do the following:
```html
<p
  *ngIf="
    profileForm.controls.dataOfBirth.errors?.['notOfAge'] &&
    profileForm.controls.dataOfBirth.dirty
  "
  class="error"
>
  You should be at least
  {{ profileForm.controls.dataOfBirth.errors?.["minAge"] }} years old.
</p>
```
When the age is not valid, it will be indicated in the form.

![-](https://blog.openreplay.com/images/handling-forms-and-validation-in-angular/images/image08.png)



Custom validators are also used for cross-field validation, which involves validating a control's value against another. A typical example of this is the password confirmation validation, which we will be implementing.

To implement the password confirmation cross-field validation, first create the custom validator:
```javascript
export const confirmPasswordValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { 
  const password1 = control.get('password')
  const confirmPassword = control.get('confirmPassword');

  return confirmPassword?.value !== password1?.value ? {passwordNotSame: true} : null
}
```
In the above code, we are using the `FormGroup`'s [get](https://angular.io/api/forms/AbstractControl#get) method to retrieve the controls. This is because for cross-field validations, a common ancestor control FormGroup is used to perform the validation.

Now, update the form model by specifying the custom validator and adding a `password` and `confirmPassword` control. 
```javascript
profileForm = new FormGroup({
  ...
  password: new FormControl('', Validators.required),
  confirmPassword: new FormControl('', Validators.required)
}, {validators: confirmPasswordValidator});
```
Then, in the template, add the two password fields and display the password confirmation error message:
```html
<div>
  <label for="password">Password</label><br />
  <input
    type="password"
    id="password"
    formControlName="password"
    placeholder="Enter your password"
  />
</div>
<div>
  <label for="cofimrm-password">Confirm Password</label><br />
  <input
    type="password"
    id="confirm-password"
    formControlName="confirmPassword"
    placeholder="Confirm your password"
  />
  <p
    *ngIf="
      profileForm.errors?.['passwordNotSame'] &&
      profileForm.controls.confirmPassword.dirty
    "
    class="error"
  >
    Your passwrod does not match
  </p>
</div>
```
Although the password confirmation error message will display, there will be no red border around its input field. This is because in our style we are using the `ng-invalid` class, which is present in each control when it's invalid, to add the red border. Still, for cross-field validation, when the validated control is invalid, the `ng-invalid` class will only be present in the element associated with the `FormGroup`, the `form` tag in our case. 

To fix this, we can conditionally add a class when the password does not match, which will be used to add the red border. To do this, modify the `confirm password` field to the following:
```html
<input 
  type="password" 
  id="confirm-password" 
  formControlName="confirmPassword" 
  placeholder="Confirm your password" 
  [ngClass]="{'input-error': profileForm.errors?.['passwordNotSame'] && profileForm.controls.confirmPassword.dirty}"
/>
```
Next, add the following styling:
```css
.profile-form .input-error {
  border: 2px solid rgb(235, 104, 104); 
}
```

With this, when the passwords do not match, it will be indicated in the form like this:
![-](https://blog.openreplay.com/images/handling-forms-and-validation-in-angular/images/image09.png)



### Multi-step reactive form with validation
We will break down the profile form into two steps to demonstrate how to implement a multi-step form: `NAME` and `BIRTH`. `NAME` will contain the `fullname` and `username` control, and `BIRTH` will contain the date of birth control. For this tutorial, we will implement a basic multi-step form with no progress headers. If you want a more complex multi-step form, check out the [Angular Material Stepper component](https://material.angular.io/components/stepper/overview).

We will use nested groups to break down the form into steps, which involve defining other form group instances within a parent form group. Nested form groups make creating complex models easier to maintain and logically group together. However, this is for demonstration purposes for our use case because the profile form isn't complex enough to use nesting.

For implementing the multi-step form, we need to conditionally render the steps of a form based on the value of a numeric variable, which will be decreased or increased by one when the previous or next button is clicked.

In the `profile-form.component.ts`, modify the component to the following:
```javascript
export class ProfileFormComponent {
  
  profileForm = new FormGroup({
    name: new FormGroup({
      fullname: new FormControl('', [
        Validators.required,
        Validators.minLength(5)
      ]),
      username: new FormControl('', Validators.required),
    }),
    dataOfBirth: new FormControl('', Validators.required)
  });

  name = this.profileForm.controls.name
  step = 1;

  previousStep () {
    if (this.step !== 1) this.step -= 1 
  }

  nextStep () {
    if(this.step !== 2) this.step += 1
  }
  
  stepTitle () {
    switch (this.step){
      case 1: return 'NAME'
      case 2: return 'BIRTH'
      default: return ''
    }
  }

  onSubmit() {
    console.log(
      `
        Full Name is ${this.name.value.fullname}
        Username is ${this.name.value.username}
        Date of birth is ${this.profileForm.value.dataOfBirth}
      `
    )
  }
}
```
In the above code, we have nested the `fullname` and `username` in a child group called `name` and created a variable to access the `name` group easily. After that, we defined a `step` property which will be used to render each step in the form conditionally, and two methods: `previousStep` and `nextStep` to decrease or increase the step by one (1), respectively, and a `stepTitle` method which returns the title of each step.

Next, modify the `<div>` tag with the class name of `profile-form` in `profile-form.component.html` to the following:
```html
<div class="profile-form">
  <h2>{{ stepTitle() }}</h2>
  <form [formGroup]="profileForm" (ngSubmit)="onSubmit()" name="profile-form">
    <div *ngIf="step === 1" formGroupName="name">
      <div>
        <label for="full-name">Full name</label><br />
        <input
          type="text"
          id="full-name"
          formControlName="fullname"
          placeholder="Enter your full name"
        />
        <div
          *ngIf="name.controls.fullname.invalid && name.controls.fullname.dirty"
          class="error"
        >
          <p *ngIf="name.controls.fullname.errors?.['required']">
            Fullname is required.
          </p>
          <p *ngIf="name.controls.fullname.errors?.['minlength']">
            Fullname must be at least 5 characters long.
          </p>
        </div>
      </div>
      <div>
        <label for="username">Username</label><br />
        <input
          type="text"
          id="username"
          formControlName="username"
          placeholder="Enter your username"
        />
        <p
          *ngIf="
            name.controls.username.errors?.['required'] &&
            name.controls.username.dirty
          "
          class="error"
        >
          Username is required.
        </p>
      </div>
    </div>
    <div *ngIf="step === 2">
      <label for="birth">Date of Birth</label><br />
      <input
        type="date"
        id="birth"
        formControlName="dataOfBirth"
        placeholder="date of birth"
      />
      <p
        *ngIf="
          profileForm.controls.dataOfBirth.errors?.['required'] &&
          profileForm.controls.dataOfBirth.dirty
        "
        class="error"
      >
        Date of birth is required.
      </p>
    </div>
    <div class="buttons">
      <button type="button" (click)="previousStep()">Prev</button>
      <button type="button" (click)="nextStep()">Next</button>
      <button *ngIf="step === 2" [disabled]="profileForm.invalid">Done</button>
    </div>
  </form>
</div>
```
In the above code, we called `stepTitle()` to show the title of the current step, hide or show the controls of each step based on the value of the `step` property, and add a Previous and Next button which calls,, the corresponding method when clicked, and show the submit button only at the ending of the form.
```css
<style>
  .profile-form {
    max-width: 400px;
    box-shadow: 0 4px 4px rgb(179, 184, 189);
    padding: 50px 20px;
    margin: 0 auto;
    margin-top: 100px;
  }

  .profile-form h2 {
    font-family: monospace;
    font-size: 24px;
    text-align: center;
  }

  .group {
    display: flex;
    flex-direction: column;
    gap: 20px;
  }

  .profile-form input {
    border: 1px solid rgb(179, 184, 189);
    height: 50px;
    width: 100%;
    font-size: 16px;
    margin-top: 5px;
    padding: 0 10px;
    box-sizing: border-box;
  }

  .profile-form input:focus {
    outline: none;
  }

  .profile-form button {
    background-color: rgb(0, 119, 255);
    padding: 15px 25px;
    width: fit-content;
    color: white;
    border: none;
    border-radius: 4px;
  }

  .profile-form input.ng-invalid.ng-dirty {
    border: 2px solid rgb(235, 104, 104); 
  }

  .error {
    color:rgb(235, 104, 104)
  }

  .buttons {
    margin-top: 20px;
    display: flex;
    gap: 10px;
    justify-content: right;
  }
</style> 
```
With this, the form will look like the following:

![-](https://blog.openreplay.com/images/handling-forms-and-validation-in-angular/images/image10.gif)


Although we have implemented the multi-form, even when the fields are empty, we can still click the next button, and there are no visual cues to indicate when a button cannot be clicked.

To fix this, we will add a `disabled` attribute to all the buttons and set it to false or true based on whether a particular step is valid. When the `disabled` attribute is true, it is added to the element; when it is false, it is not added. So, we will use the presence of the disabled property to change the color of the button to gray to indicate that it can't be clicked.

First, in `profile-form.component.ts`, add the following method before the `onSubmit` method:
```javascript
stepIsValid () {
  console.log(this.name)
  if(this.step === 1) {
    return (
      !!this.name.value.fullname 
      && !!this.name.value.username
      && !this.name.controls.fullname.errors
      && !this.name.controls.username.errors
    )
  }else {
    return (
      false
    )
  }
}
```
This method returns true if the controls in a step are not empty and have no errors; if not, it returns false. 

Next, in `profile-form.component.html`, modify the previous and next buttons to the following:
```html
<button type="button" [disabled]="step === 1" (click)="previousStep()">
  Prev
</button>
<button
  type="button"
  [disabled]="!stepIsValid(profileForm)"
  (click)="nextStep()"
>
  Next
</button>
```
In the above code, we disable the previous button if the form is in the first step and the next button using the `stepIsValid()` method returns false.

Lastly, add the following styling to the `<style>` tag.

```css
.profile-form button[disabled] {
  background-color: gainsboro;
}
```
With this, the form will look like the following:

![-](https://blog.openreplay.com/images/handling-forms-and-validation-in-angular/images/image11.gif)


## Conclusion

Both template-driven and reactive forms are great options for adding a form to your Angular app, but depending on your use case, you must choose one. So make sure before building your forms, you compare to see which is best for your app. You can learn more about forms in the [Angular forms doc](https://angular.io/guide/forms-overview).