Best Practices for Handling Form Validation in Angular

    Tuesday, March 12, 202415 min read487 views
    Best Practices for Handling Form Validation in Angular

    Table of Contents

    1. Introduction

    2. Implementation of Angular Forms

    3. Types of Angular Forms

    4. Validations in Angular Forms

    5. Why Use Angular Forms?

    6. Conclusion

    Handling of the form is one of the pivotal phases where the user's input field is received by the web application. Now, these inputs can be used for different purposes, for instance, the process of login or registration, feedback, data entry tasks, etc.

    In the Angular framework, we are provided with built-in template-driven form modules which help us to create forms with fewer code snippets. So as an Angular developer, it makes more sense to use Angular form rather than using normal form, while developing web applications.

    The reason behind it is very vast as it includes basic built-in validations, dynamic adding of inputs, dynamic removal of validations, creating our validations, etc. Whether handling simple data entry or complex form structures, Angular's robust form features improve the process and enhance the overall user experience in any SRC app.

    Now, the implementation of these Angular forms is painless in our angular application. We can initiate by importing the modules viz. FormsModule and ReactiveFormsModule.

    Handling form validation in Angular

    Implementation of Angular Forms

    Angular supports two ways of angular forms implementation, which are stated below.

    1. Mandatory Modules

    FormsModule and ReactiveFormsModule are the two modules provided by Angular. Each module has different use cases based on different scenarios. Both of these modules are present in the '@angular/forms' package.

    We can use these modules either by importing them into the component's module or importing them into the component's imports property if our component is standalone.

    2. Code-snippet Representation

    // Importing modules in a component's module.
    
    import { FormsModule, ReactiveFormsModule }   from '@angular/forms';
    import { BrowserModule } from '@angular/platform-browser';
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    
    @NgModule({
        imports: [BrowserModule, FormsModule, ReactiveFormsModule],
        ...More settings
    })
    export class AppModule { }

    // Importing modules in a standalone component.
    
    import { Component } from '@angular/core';
    import { FormsModule, ReactiveFormsModule }   from '@angular/forms';
    
    @Component({
      selector: 'app',
      template: 'Hello World',
      standalone : true,
      imports : [FormsModule, ReactiveFormsModule]
    })
    
    export class AppComponent {
      constructor() {
      }
    }

    Types of Angular Form

    1. Template-driven Form

    The template-driven form model can be used after importing the FormsModule into our angular application. This type of form is used in scenarios where we want to create small forms like a login form or any other form that includes a limited count of input fields. The whole form control handling is taken by the templates here. Operations like checking validation status, creating forms, etc. are performed inside the template.

    In these forms, ngForm plays a key role during form handling. NgForm is a built-in directive provided by Angular that creates an instance of FormGroup which binds to the form to track the form value and validations.

    Below code snippet below shows the implementation of Template-driven forms in a component's template.

    <!-- We should use ngForm on a form tag only. -->
    <form #formInstance="ngForm">
          <label for="e-mail">Email </label>
          <input id="e-mail" name="Email" ngModel/>
          <br/> <br/>
          <label for="password">Password </label>
          <input id="password" name="Password" type="password" ngModel/>
    </form>

    HTML output

    From the above code snippet, we can now use the variable formInstance for various purposes. For example, we can check the validity of the form and check the validity of each form control.

    The mandatory thing to do here is to provide the value for the attribute name. If we skip providing the value for the name attribute, then we should use ngModelOptions, to which we should assign an object containing a property viz. standalone having a value as true. By doing so, our form control becomes standalone and hence our variable cannot be accessed further.

    To submit our form, the (ngSumbit) output property is used. Pressing the enter key from the keyboard leads to the submission of the form or in other words, the form's submit method is fired.

    2. Reactive Forms

    Reactive forms can be used after importing the ReactiveFormsModule into our project. This type of form is used in scenarios where we want to add dynamic input form control to our form data model.

    The whole form control handling is taken by the ts file or component class here. Operations like checking validation, creating forms, etc. are performed inside the component class.

    One way to create a reactive form is using the instance of the component class FormGroup. The other way is to use the FormBuilder service's form group() method. Both of these classes can be found in the '@angular/forms' package.

    The below code snippet shows the implementation of reactive forms using both ways mentioned above.

    //Using FormGroup instance
    
    import { Component } from '@angular/core';
    import { FormControl, FormGroup } from '@angular/forms';
    @Component({
      selector: 'app-registration-form',
      template : `
    	<form [formGroup]="registrationForm">
            	<label for="f-name">FirstName </label>
            	<input formControlName="firstName"  id="f-name"/>
          		<br/> <br/>
            	<label for="l-name">LastName </label>
            	<input formControlName="lastName" id="l-name"/>
        	</form>
    	`
    })
    export class RegistrationFormComponent {
      registrationForm!: FormGroup;
      ngOnInit(): void {
        this.registrationForm = new FormGroup({
          firstName: new FormControl(''),
          lastName: new FormControl(''),
        });
      }
    }

    //Using FormBuilder service
    
    import { Component } from '@angular/core';
    import { FormControl, FormGroup, FormBuilder } from '@angular/forms';
    @Component({
      selector: 'app-registration-form',
      template : `
    	<form [formGroup]="registrationForm">
      		<label for="f-name">FirstName</label>
      		<input formControlName="firstName"  id="f-name"/>
    		<br/>
      		<label for="l-name">LastName</label>
      		<input formControlName="lastName" id="l-name"/>
    	</form>
    	`
    })
    export class RegistrationFormComponent {
      registrationForm!: FormGroup;
      ngOnInit(): void {
        constructor(private formBuilder : FormBuilder) {}
        this.registrationForm = this.formBuilder.group({
          firstName: new FormControl(''),
          lastName: new FormControl(''),
        });
      }
    }

    HTML output of reactive forms

    The form group() method of the FormBuilder service also returns the instance of the FormGroup class. This form group() method takes a single parameter, which is an object containing keys as an instance of the FormControl class.

    To access the form control value, we should use the get() method. This method is present in the FormGroup class. The get() method takes a single argument of type string, which is the form control name.

    With the addControl() method of the FormGroup class, we can create dynamic form controls that can be added accordingly to our form data. Similarly, by using the method removeControl(), we can remove form controls from our form control data.

    The addControl() method takes two arguments, in which the first one is of type string and the second is an instance of FormControl class, whereas removeControl() takes a single argument of type string, which is the form control name.

    Validations in Angular form

    While creating any form, it is vital to validate the input elements as it prevents the user from entering any wrong information in the form data. Validations in angular forms are of two types. One is built-in validations and the other is our custom validations.

    1. Validations for Template-driven Forms

    Some examples of built-in validations that can be used by the developer in template-driven forms are mentioned below. We should provide them as a directive in the input elements.

    1. required: This validation makes user input mandatory.

    2. minlength: This validation defines a minimum length required for an input value.

    3. maxlength: This validation defines the maximum length required for an input value.

    4. email: This validation defines an input value to meet all conditions of an email.

    5. min: This validation can be used on the input elements whose input type is number. It defines a minimum number as the input value.

    6. max: Same as min validation but, it defines a maximum number as the input value.

    <form #formInstance="ngForm">
      <input name="name" placeholder="Enter Name" ngModel #nameI="ngModel" 
    	 required/>
      <br/>
      <small *ngIf="nameI.errors?.['required']">Mandatory field</small>
      <br/><br/>
    
      <input name="email" placeholder="Enter Email" ngModel #emailI="ngModel" email
             required/>
      <br/>
      <small *ngIf="emailI.errors?.['email'] || emailI.errors?.['required']">
    	Provide valid email
      </small>
    
      <br/><br/>
      <input name="age" placeholder="Enter Age" type="number" ngModel 
    	 #ageI="ngModel" min="18" value="0"/>
      <br/>
      <small *ngIf="ageI.errors?.['min']">Minumum age should be 18</small>
    
      <br/><br/>
      <textarea placeholder="Write Description" name="desc"
                rows="5" minlength="25" ngModel #descI="ngModel"></textarea>
      <br>
      <small *ngIf="descI.errors?.['minlength']">
    	Description must be lengthy.
      </small>
    </form>

    HTML output representing validations in template-driven froms.

    2. Validations for reactive forms

    Some examples of built-in validations that can be used by the developer in reactive forms are mentioned below. These validations are static methods of class Validators. Validators class can be imported from the package '@angular/forms'.

    Some examples of built-in validations that can be used by the developer in reactive forms are mentioned below.

    1. required: This validation makes user input mandatory.

    2. minlength: This validation defines a minimum length required for an input value.

    3. maxlength: This validation defines the maximum length required for an input value.

    4. email: This validation defines an input value to meet all conditions of an email.

    5. min: This validation can be used on the input elements whose input type is number. It defines a minimum number as the input value.

    6. max: Same as min validation but, it defines a maximum number as an input value.

    We should include these validations inside the validation array. For example, if we want to use the 'required' validation, it can done like 'Validators.required'. We should pass this to the validation array.

    import { Component } from '@angular/core';
    import {
      ReactiveFormsModule,
      FormControl,
      FormGroup,
      Validators,
      FormArray,
    } from '@angular/forms';
    
    @Component({
      selector: 'app-root',
      template: `
        <form [formGroup]="formGroup">
          <input name="name" placeholder="Enter Name" formControlName="name" />
          <br>
          <small *ngIf="formGroup.controls['name'].errors?.['required']">
    	Name is mandatory
          </small>
          <br><br/>
          <button (click)="addNewSkill()">Add new skill</button><br/><br/>
          <div formArrayName="skills">
            <div *ngFor="let x of skills.controls; index as i">
              <input [formControlName]="i" placeholder="Enter a skill"/>
              <br/><br/>
            </div>
          </div>
        </form>
      `,
      styles: `
      small {
        color : red;
      }
      `,
    })
    export class App {
      formGroup!: FormGroup;
      skills!: FormArray;
      ngOnInit(): void {
        this.skills = new FormArray([new FormControl('')]);
        this.formGroup = new FormGroup({
          name: new FormControl('', [Validators.required]),
          skills: this.skills,
        });
      }
    
      addNewSkill(): void {
        this.skills.push(new FormControl('', [Validators.required]));
      }
    }

    HTML output representation

    Why Use Angular Forms?

    1. No need to write boiler-plate code

    The functionalities like validation handling, synchronizing form control values with the input elements of form data, etc. are all handled by angular itself. This helps a developer to interact less with input elements and focus more on functionalities.

    2. Validation status can be checked synchronously

    In both types of angular forms, we can check the user input value and validation status in parallel. This helps us to notify the user way before he submits the form data if he/she types a wrong input. Angular forms also allow us to create validations that can be applied to input elements. These validations also work synchronously and emit validation errors whenever there's a wrong input placed by the user.

    3. Dynamic contents can be added to form data easily

    In reactive forms, we can add new input form control dynamically. This is possible through various ways. The one way is through the FormArray instance. Now, this instance has many useful methods like push() which adds a new form control or form group instance to the FormArray. Also, we have removeAt() which removes the particular item from FormArray.

    import 'zone.js';
    import { Component } from '@angular/core';
    import {
      FormsModule,
      ReactiveFormsModule,
      FormControl,
      FormGroup,
      Validators,
      FormArray,
    } from '@angular/forms';
    
    @Component({
      selector: 'app-root',
      template: `
        <form [formGroup]="formGroup">
          <input name="name" placeholder="Enter Name" formControlName="name" />
          <br><br/>
          <button (click)="addNewSkill()">Add new skill</button><br/><br/>
          <div formArrayName="skills">
            <div *ngFor="let x of skills().controls; index as i">
              <input [formControlName]="i" placeholder="Enter a skill"/>
              <br/><br/>
            </div>
          </div>
        </form>
      `,
    })
    export class App {
      formGroup!: FormGroup;
      skills!: FormArray;
      ngOnInit(): void {
        this.skills = new FormArray([new FormControl('')]);
        this.formGroup = new FormGroup({
          name: new FormControl(''),
          skills: this.skills,
        });
      }
    
    addNewSkill(): void {
        this.skills.push(new FormControl(''));
      }
    }

    HTML output for form using FormArray class.

    Final Thoughts

    Angular forms i.e. both template-driven and reactive, offer a multitude of advantages for developers.

    The template-driven forms are suitable for small forms with limited fields and reactive forms are suitable for complex forms with complex form control validations. FormsModule and ReactiveFormsModule provide a set of tools like ngForm, ngModel, etc. for handling form controls.

    It also provides several built-in validations that are required to validate the forms. One of the best parts of reactive form is that we can add or remove form controls dynamically. Thus, it creates a dynamic and user-interactive interface. Creating form controls, adding, removing, etc. into form data is possible through FormArray and its corresponding methods.

    For more updates, insights, and services, get in touch with us at Angular Minds.

    24

    Related articles

    This website uses cookies to analyze website traffic and optimize your website experience. By continuing, you agree to our use of cookies as described in our Privacy Policy.