Mastering Strictly Typed Reactive Forms in Angular: A Step-by-Step Guide
Tags: angular, development, webdev
Published: 2023-01-18 13:09:57

---
title: Mastering Strictly Typed Reactive Forms in Angular: A Step-by-Step Guide
published: true
date: 2023-01-18 10:30:00 UTC
tags: angular, development, webdev
canonical_url: https://www.syncfusion.com/blogs/post/strictly-typed-reactive-forms-in-angular.aspx
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wyhn5uth0e56263ep80f.png
---
In this article, we will learn how to create strictly typed reactive forms in Angular. We will create a student registration form as an example. The form will have support for built-in form validations. We will also create asynchronous custom validators for the form.

The following custom validations will be implemented on the reactive form:

- Password pattern validation.
- Match passwords in two different fields.
- Check for username availability.

## Prerequisites

We will set up the Angular development environment on our machine:

1. Install [Node.js](https://nodejs.org/en/ "Node.js").
2. Install the latest version of [Angular CLI](https://angular.io/cli "Angular CLI Overview and Command Reference").

In this tutorial, we will be using Visual Studio Code as our preferred IDE. Please install the latest version from the [Visual Studio Code download page](https://code.visualstudio.com/ "Download Visual Studio Code").

## Creating the Angular application

Use the [Angular CLI](https://angular.io/cli "Angular CLI Overview and Command Reference") to create a new Angular application.

Open a command window inside the folder where you want to create the application. Run the following command.

```
ng new typed-reactive-forms --routing=false --style=scss
```

The routing option allows us to create a routing module for the application. However, this is a small demo app. So, we do not need to add a separate module for routing. The style option helps us to specify the file extension or preprocessor that we use for style files.

Run the following command to change the directory to the new project.

```
cd typed-reactive-forms
```

Open the project in VS Code using the following command.

```
code .
```

### Install the Syncfusion Angular UI Components

We will use the Syncfusion Angular components for styling the form. Run the following command to install the necessary NuGet packages to create a form.

```
npm i @syncfusion/ej2-angular-buttons @syncfusion/ej2-angular-inputs
```

### Configure the App module

Now, import the modules for reactive forms and the Syncfusion Angular components in the app module file.

Add the following code in the **app.module.ts** file.

```js
import { ReactiveFormsModule } from '@angular/forms';
import { ButtonModule } from '@syncfusion/ej2-angular-buttons';
import { TextBoxModule } from '@syncfusion/ej2-angular-inputs';
...
@NgModule({
  ...
  imports: [
    ...
    ReactiveFormsModule,
    ButtonModule,
    TextBoxModule,
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}
```
### Create the custom validation service

We will use a service to create custom validators for our reactive form.

Run the following command to create a new service.

```
ng g s services/custom-form-validator
```

Executing this command will create a folder named **services** and create the service file **custom-form-validator.service.ts** inside it.

Update the **CustomFormValidatorService** class by adding the following code.

```js
export class CustomFormValidatorService {
  passwordPatternValidator(): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      if (!control.value) {
        return null;
      }
      const regex = new RegExp('^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$');
      const validPattern = regex.test(control.value);
      return validPattern ? null : { invalidPassword: true };
    };
  }
  
  matchPasswordValidator(password: string, confirmPassword: string) {
    return (formGroup: AbstractControl): ValidationErrors | null => {
      const passwordControl = formGroup.get(password);
      const confirmPasswordControl = formGroup.get(confirmPassword);
      
      if (!passwordControl || !confirmPasswordControl) {
        return null;
      }
      
      if (
        confirmPasswordControl.errors &&
        !confirmPasswordControl.errors['passwordMismatch']
      ) 
      {
        return null;
      }
      
      if (passwordControl.value !== confirmPasswordControl.value) {
        confirmPasswordControl.setErrors({ passwordMismatch: true });
        return { passwordMismatch: true };
      } 
      else {
        confirmPasswordControl.setErrors(null);
        return null;
      }
    };
  }
}
```

Here, we use the **passwordPatternValidator** function to validate the strength of the password entered by the user. This function will accept a parameter of type **AbstractControl**. In this function, we will use regex to check for the strength of the password based on the following criteria:

- Must have a minimum of eight characters.
- It should have at least one lowercase letter.
- It should have at least one uppercase letter.
- It should contain at least one number.

In case the password entered by the user fails the regex check, we will set the **invalidPassword** property to **true**.

The function **matchPasswordValidator** helps us to check if the password entered in two fields of the form is an exact match.

### Create asynchronous validator

An asynchronous validator helps us to validate the values entered in the form field against an asynchronous source of data, such as a value coming via an HTTP call.

Run the following command to create a new service.

```
ng g s services\user-name-validation
```

Update the **UserNameValidationService** class by adding the following code.

```js
export class UserNameValidationService implements AsyncValidator {
  validate(control: AbstractControl): Observable<ValidationErrors | null> {
    return this.validateUserName(control.value).pipe(
      map((duplicateUserName) => {
        if (duplicateUserName) {
          return { userNameNotAvailable: true };
        } 
        else {
          return null;
        }
      }),
      catchError(() => of(null))
    );
  }
  
  private validateUserName(username: string): Observable<boolean> {
    const UserList = ['admin', 'user', 'superuser'];
    return of(UserList.includes(username.toLocaleLowerCase()));
  }
}
```

A custom async validator must implement the **AsyncValidator** interface and should override the **validate()** function provided by the interface.

The **UserNameValidationService** helps us to validate if the username entered in the form is available or not. The validate function will accept a parameter of type **AbstractControl**.

The **validateUserName** function is used to match the value entered in the form against a static array. If the value entered by the user is already present in the array, we will set the **userNameNotAvailable** property to true.

**Note:** For the simplicity of this blog, we are verifying the availability of the username against a list of static values. However, in an ideal scenario, we should check this against a database via an API call.

### Create the interface

Add a new folder named model inside the src folder. Create a file named **studentRegistration.ts** and add the following code inside it.

```js
import { FormControl } from '@angular/forms';

export interface StudentRegistration {
  firstName: FormControl<string>;
  lastName: FormControl<string>;
  email: FormControl<string>;
  username: FormControl<string>;
  password: FormControl<string>;
  confirmPassword: FormControl<string>;
  age: FormControl<number>;
}
```

So, we have created an interface to define the type of our form. This interface will contain all the fields of the form.

### Creating the registration component

Run the following command to create the registration component. This component will allow us to register a new student’s record.

```
ng g c registration
```

Add the following code inside the **RegistrationComponent** class.

```js
protected studentRegistrationForm!: FormGroup<StudentRegistration>;
protected submitted = false;
  
constructor(
    private readonly formBuilder: NonNullableFormBuilder,
    private readonly customFormValidator: CustomFormValidatorService,
    private readonly userNameValidationService: UserNameValidationService
) 
{
   this.initializeForm();
}

private initializeForm(): void {
  this.studentRegistrationForm = this.formBuilder.group(
  {
     firstName: this.formBuilder.control('', Validators.required),
     lastName: this.formBuilder.control('', Validators.required),
     email: this.formBuilder.control('', [
         Validators.required,
         Validators.email,
     ]),
     username: this.formBuilder.control('', {
       asyncValidators: [
         this.userNameValidationService.validate.bind(
           this.userNameValidationService
         ),
       ],
       validators: [Validators.required],
       updateOn: 'blur',
     }),
     password: this.formBuilder.control('', [
       Validators.required,        
       this.customFormValidator.passwordPatternValidator(),
     ]),
     confirmPassword: this.formBuilder.control('', Validators.required),
     age: this.formBuilder.control(14, [
        Validators.required,
        Validators.min(14),
        Validators.max(25),
     ]),
  },
  {
    validators: [
      this.customFormValidator.matchPasswordValidator(
        'password',
        'confirmPassword'
      ),
    ],
  }
 );
}
```

Now, we have created a strongly typed FormGroup of type **StudentRegistration**.

The **NonNullableFormBuilder** class helps us to construct a nonnullable form control. If we invoke the reset function on a nonnullable form control, then the value will be set to the initial value.

Here, all the form controls are marked as required. The email form control has a validation to match the valid email address.

The username control has both synchronous and asynchronous validations attached to it. An asynchronous validation always happens after the synchronous validation, and it is performed only if the synchronous validation is successful.

We have invoked the custom **passwordPatternValidator** on the password control.

The age form control has the min and max values defined, and this field will only accept a value within the given range.

A cross-field validator is a custom validator that can compare the values of multiple form controls. We have added the cross-field validator to the password and **confirmPassword** fields to match their values.

Using the **NonNullableFormBuilder** class to construct the form is optional. If you want to allow nullable values for your form control, then you can use the **FormBuilder** class. However, to allow the nullable values, you should update the interface as shown in the following. Everything else should remain the same.

```js
export interface StudentRegistration {
  firstName: FormControl<string | null>;
  lastName: FormControl<string | null>;
  email: FormControl<string | null>;
  username: FormControl<string | null>;
  password: FormControl<string | null>;
  confirmPassword: FormControl<string | null>;
  age: FormControl<number | null>;
}
```

Add the **onSubmit** function, as shown.

```js
protected onSubmit(): void {
    this.submitted = true;
    if (this.studentRegistrationForm.valid) {
      alert('Form submitted successfully!!!');
      console.table(this.studentRegistrationForm.value);
    }
  }
```

This function will check whether the form is valid and then print the form values on the browser console. In a real-world application, we can invoke an HTTP call and send the form data to an API.

Finally, add the following two functions.

```js
protected resetForm(): void {
    this.studentRegistrationForm.reset();
}
protected get registrationFormControl() {
    return this.studentRegistrationForm.controls;
}
```

The **resetForm** function is used to reset the values of the form control. The **registrationFormControl** is a getter function used to get the values of the form control.

Open **registration.component.html** and add the following code to it.

```xml
<div class="form-container">
  <div class="title-container">
    <h1>Student Registration</h1>
  </div>
  
  <div class="card-layout">
    <div class="e-card">
      <div class="e-card-content">
        <form [formGroup]="studentRegistrationForm" (ngSubmit)="onSubmit()">
          <div class="e-input-section">
            <ejs-textbox
              placeholder="First name"
              cssClass="e-outline"
              floatLabelType="Auto"
              formControlName="firstName"
            ></ejs-textbox>
          </div>
          <div
            *ngIf="(registrationFormControl.firstName.touched || submitted) 
            && registrationFormControl.firstName.errors?.['required']"
            class="e-error"
          >
            First name is required.
          </div>
          
          <div class="e-input-section">
            <ejs-textbox
              placeholder="Last name"
              cssClass="e-outline"
              floatLabelType="Auto"
              formControlName="lastName"
            ></ejs-textbox>
          </div>
          <div
            *ngIf="(registrationFormControl.lastName.touched || submitted) 
            && registrationFormControl.lastName.errors?.['required']"
            class="e-error"
          >
            Last name is required.
          </div>
          
          <div class="e-input-section">
            <ejs-textbox
              placeholder="Email"
              cssClass="e-outline"
              floatLabelType="Auto"
              formControlName="email"
            ></ejs-textbox>
          </div>
          <div
            *ngIf="(registrationFormControl.email.touched || submitted) 
            && registrationFormControl.email.errors?.['required']"
            class="e-error"
          >
            Email is required.
          </div>
          <div
            *ngIf="registrationFormControl.email.touched &&
            registrationFormControl.email.errors?.['email']"
            class="e-error"
          >
            Enter a valid email address.
          </div>
          
          <div class="e-input-section">
            <ejs-textbox
              placeholder="User Name"
              cssClass="e-outline"
              floatLabelType="Auto"
              formControlName="username"
            ></ejs-textbox>
          </div>
          <div
            *ngIf="(registrationFormControl.username.touched || submitted) 
            && registrationFormControl.username.errors?.['required']"
            class="e-error"
          >
            User Name is required.
          </div>
          <div
            *ngIf="registrationFormControl.username.touched &&
            registrationFormControl.username.errors?.['userNameNotAvailable']"
            class="e-error"
          >
            User Name is not available.
          </div>
          
          <div class="e-input-section">
            <ejs-textbox
              type="password"
              placeholder="Password"
              cssClass="e-outline"
              floatLabelType="Auto"
              formControlName="password"
            ></ejs-textbox>
          </div>
          <div
            *ngIf="(registrationFormControl.password.touched || submitted) 
            && registrationFormControl.password.errors?.['required']"
            class="e-error"
          >
            Password is required.
          </div>
          <div
            *ngIf="registrationFormControl.password &&
            registrationFormControl.password.errors?.['invalidPassword']"
            class="e-error"
          >
            Password should have minimum 8 characters, at least 1 uppercase
            letter, 1 lowercase letter and 1 number.
          </div>
          
          <div class="e-input-section">
            <ejs-textbox
              type="password"
              placeholder="Confirm password"
              cssClass="e-outline"
              floatLabelType="Auto"
              formControlName="confirmPassword"
            ></ejs-textbox>
          </div>
          <div
            *ngIf="(registrationFormControl.confirmPassword.touched || submitted) 
            && registrationFormControl.confirmPassword.errors?.['required']"
            class="e-error"
          >
            Confirm password is required.
          </div>
          <div
            *ngIf="registrationFormControl.confirmPassword.touched &&
            registrationFormControl.confirmPassword.errors?.['passwordMismatch']"
            class="e-error"
          >
            Passwords does not match.
          </div>
          
          <div class="e-input-section">
            <ejs-textbox
              type="number"
              placeholder="Age"
              cssClass="e-outline"
              floatLabelType="Auto"
              formControlName="age"
            ></ejs-textbox>
          </div>
          <div
            *ngIf="(registrationFormControl.age.touched || submitted) &&
            registrationFormControl.age.errors?.['required']"
            class="e-error"
          >
            Age is required.
          </div>
          <div
            *ngIf="(registrationFormControl.age.touched || submitted) && 
            (registrationFormControl.age.errors?.['min'] || registrationFormControl.age.errors?.['max'])"
            class="e-error"
          >
            Age should be greater than 14 years and less than 25 years.
          </div>
          
          <div class="e-card-actions">
            <button type="submit" ejs-button cssClass="e-success">
              Register
            </button>
            <button
              type="reset"
              ejs-button
              cssClass="e-warning reset-button"
              (click)="resetForm()"
            >
              Reset
            </button>
          </div>
        </form>
      </div>
    </div>
  </div>
</div>
```

We have created a form and added the error message to be displayed when there are errors in the form control. We have used the **formControlName** property to bind the form fields in the template to the form controls of our FormGroup **studentRegistrationForm**.

The **onSubmit** function will be invoked when we submit the form. Clicking the Reset button will invoke the **resetForm** function.

## Update the app component

Open the **app.component.html file** and replace the existing code with the selector for the registration component, as shown.

```
<app-registration></app-registration>
```

## Execution demo

Run the following command to start the application.

```
ng serve
```

Open [http://localhost:4200/](http://localhost:4200/) in the browser. You can see the output displayed in the following.

![Creating Student Registration Form](https://www.syncfusion.com/blogs/wp-content/uploads/2023/01/Creating-Student-Registration-Form.gif)

## Resource

The complete source code of this application is available on [GitHub](https://github.com/SyncfusionExamples/strictly-typed-reactive-forms-in-angular "Strictly Typed Reactive Forms in Angular").

The application is hosted at [https://aquamarine-cassata-be3458.netlify.app/](https://aquamarine-cassata-be3458.netlify.app/ "Student Registration Demo"). Navigate to the website and play around for a better understanding.

## Summary

Thanks for reading! In this blog, we learned how to create strictly typed reactive forms, with an example of a student registration form. We implemented built-in as well as custom validations on the form.

Syncfusion’s [Angular UI component](https://www.syncfusion.com/angular-ui-components "Syncfusion Angular UI components") library is the only suite you will ever need to build an app. It contains over 75 high-performance, lightweight, modular, and responsive UI components in a single package.

For existing customers, the newest Essential Studio version is available for download from the [License and Downloads](https://www.syncfusion.com/account/downloads "License and download page of Essential Studio") page. If you are not yet a Syncfusion customer, you can try our 30-day [free trial](https://www.syncfusion.com/downloads "Free evaluation of the Syncfusion Essential Studio") to check out the available features. Also, check out our demos on [GitHub](https://github.com/syncfusion "Syncfusion components examples on GitHub").

If you have questions, you can contact us through our [support forums](https://www.syncfusion.com/forums "Syncfusion Support Forums"), [support portal](https://support.syncfusion.com/ "Syncfusion Support Portal"), or [feedback portal](https://www.syncfusion.com/feedback/angular "Syncfusion Feedback Portal"). We are always happy to assist you!

## Related blogs

- [Debug Angular Apps Easily Using Angular DevTools](https://www.syncfusion.com/blogs/post/debug-angular-apps-easily-using-angular-devtools.aspx "Blog: Debug Angular Apps Easily Using Angular DevTools")
- [Easy Steps to Create a Read-Only Angular PDF Viewer](https://www.syncfusion.com/blogs/post/easy-steps-to-create-a-read-only-angular-pdf-viewer.aspx "Blog: Easy Steps to Create a Read-Only Angular PDF Viewer")
- [Easily Generate Bulk Reports Using Mail Merge in Angular](https://www.syncfusion.com/blogs/post/easily-generate-bulk-reports-using-mail-merge-in-angular.aspx "Blog: Easily Generate Bulk Reports Using Mail Merge in Angular")
- [A Complete Guide to Creating a Mind Map Using Angular Diagram Component](https://www.syncfusion.com/blogs/post/mind-map-using-angular-diagram.aspx "Blog: A Complete Guide to Creating a Mind Map Using Angular Diagram Component")