Skip to main content

Angular FormArray Validation

FormArray tracks the value and validity state of an array of FormControlFormGroup or FormArray instances. To create a FormArraywe can pass an array of FormControl or FormGroup. A FormArray will be called validated only if its FormControl or FormGroup are validated. To provide a custom error messages on UI, we can fetch FormArray validation status in HTML template. 
On this page we will create a reactive form using FormBuilder and then we will validate and submit the form.

Technologies Used

Find the technologies being used in our example. 
1. Angular 8.0.3 
2. TypeScript 3.4.3 
3. Node.js 12.5.0 
4. Angular CLI 8.0.6

FormArray Validation

We can pass following arguments to instantiate FormArray.
FormArray(controls: AbstractControl[],
 validatorOrOpts?: ValidatorFn | AbstractControlOptions | ValidatorFn[], 
 asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]) 
1. First argument is array of controls such as FormControlFormGroup or FormArray
2. Second argument is for sync validators. It is optional. 
3. Third argument is for async validators. It is also optional. 

Find the sample example. 
We are using Validators.required at FormArray level. It means the size of FormArray must be greater than zero.
formArray = new FormArray([], [Validators.required]);
ngOnInit() {
	console.log(this.formArray.status);
	this.formArray.push(new FormControl());
	console.log(this.formArray.status);
} 
Output
INVALID
VALID 
Look into output, when there was no element in FormArray then validity status was invalid and when we pushed a control then FormArraybecame valid.

FormArray Validation using FormControl

Suppose FormArray is the array of FormControl. Then FormArray will not be considered validated if any of the FormControl is invalid. Find the code snippet.
formArray = new FormArray([
	new FormControl('', [Validators.required]),
	new FormControl('', [Validators.required, Validators.minLength(3)])
]);

ngOnInit() {
	console.log(this.formArray.status);
	this.formArray.setValue(["AA", "BBB"]);
	console.log(this.formArray.status);		
} 
Find the output.
INVALID
VALID 
FormArray is containing two FormControl and they need to be validated and only when FormArray will be considered validated.

FormArray Validation using FormGroup

Suppose FormArray contains the array of FormGroup and each FormGroup contains FormControl with some validators. FormArraywill be considered validated only if all the FormGroup are validated and FormGroup will be validated only if all the FormControl are validated. Find the code snippet.
formArray = new FormArray([
	new FormGroup({
		name: new FormControl('', [Validators.required]),
		age: new FormControl('', [Validators.min(18)])
	}),
	new FormGroup({
		name: new FormControl('', [Validators.required]),
		age: new FormControl('', [Validators.min(18)])
	})],		
);

ngOnInit() {
	console.log(this.formArray.controls[0].status);
	console.log(this.formArray.controls[1].status);
	console.log(this.formArray.status);

	this.formArray.setValue([{name: "Mahesh", age: 20}, {name: "Krishna", age: 25}]);

	console.log("---After setting value---");
	console.log(this.formArray.controls[0].status);
	console.log(this.formArray.controls[1].status);	
	console.log(this.formArray.status);		
} 
Find the output.
INVALID
INVALID
INVALID
---After setting value---
VALID
VALID
VALID 

FormArray Validation Example with FormBuilder

We will create a reactive form using FormBuilder which has methods such as group() to create FormGroupcontrol() to create FormControl and array() to create FormArray
In our example we will create FormArray that will contain array of FormGroup and each FormGroup will contain some FormControl with validators. At run time we will push and remove controls from FormArray instance. 
Find the code snippet to create team form.
this.teamForm = this.formBuilder.group({
	teamName: ['', Validators.required],
	employees: this.formBuilder.array(
		[this.createEmpFormGroup()],
		[Validators.required])
}); 
The createEmpFormGroup() method is creating a FormGroup as following. This will add an employee in team.
createEmpFormGroup() {
	return this.formBuilder.group({
		empName: ['', [Validators.required]],
		age: ['', [Validators.required, Validators.min(21)]],
		skill: ['', [Validators.required]],
	})
} 
To access validation state of FormArray and its elements, find the getter method for employeesform array control.
get employees(): FormArray {
	return this.teamForm.get('employees') as FormArray;
} 
Now in HTML template we can access FormArray validation errors for custom error messages as following.
<label *ngIf="employees.errors?.required">
  Add Employees.
</label>
<label *ngIf="employees.controls[i].get('age').errors?.required">
  Age required.
</label>
<label *ngIf="employees.controls[i].get('age').errors?.min">
  Minimum age is 21.
</label> 
In the above code i is the index of employeesform array. 
Find the print screen of output of our example. When we try to submit the form without validations, we will get following error messages.
Angular FormArray Validation
Now find the complete code. 
team-management.component.ts
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormArray, Validators, FormBuilder } from '@angular/forms';
import { Observable } from 'rxjs';

import { TeamManagementService } from './team-management.service';
import { Team } from './team';
import { Employee } from './employee';

@Component({
	selector: 'app-team',
	templateUrl: './team-management.component.html'
})
export class TeamManagementComponent implements OnInit {
	teamForm: FormGroup;
	isValidFormSubmitted = null;
	allSkills: Observable<any[]>;
	constructor(
		private formBuilder: FormBuilder,
		private teamMngService: TeamManagementService) {
	}
	ngOnInit() {
		this.allSkills = this.teamMngService.getSkills();

		this.teamForm = this.formBuilder.group({
			teamName: ['', Validators.required],
			employees: this.formBuilder.array(
				[this.createEmpFormGroup()],
				[Validators.required])
		});
	}
	createEmpFormGroup() {
		return this.formBuilder.group({
			empName: ['', [Validators.required]],
			age: ['', [Validators.required, Validators.min(21)]],
			skill: ['', [Validators.required]],
		})
	}
	get teamName() {
		return this.teamForm.get('teamName');
	}
	get employees(): FormArray {
		return this.teamForm.get('employees') as FormArray;
	}
	addEmployee() {
		let fg = this.createEmpFormGroup();
		this.employees.push(fg);
	}
	deleteEmployee(idx: number) {
		this.employees.removeAt(idx);
	}
	onFormSubmit() {
		this.isValidFormSubmitted = false;
		if (this.teamForm.invalid) {
			return;
		}
		this.isValidFormSubmitted = true;
		let team: Team = this.teamForm.value;
		this.teamMngService.saveTeam(team);
		this.teamForm.reset();
	}
	resetTeamForm() {
		this.teamForm.reset();
	}
} 
team-management.component.html



<h3>Create New Team</h3>
<div *ngIf="isValidFormSubmitted" class="submitted"> Form submitted successfully. </div>
<div class="team">
  <form [formGroup]="teamForm" (ngSubmit)="onFormSubmit()">
    <p>Team Name :
      <input formControlName="teamName">
      <br/>
      <label *ngIf="teamName.invalid && isValidFormSubmitted != null && !isValidFormSubmitted" class="error">
        Team name is required.
      </label>
    </p>
    <div class="all-emp">
      <b>Employees in Team:</b>
      <br>
      <label *ngIf="employees.errors?.required && isValidFormSubmitted != null && !isValidFormSubmitted" class="error">
        Add Employees.
      </label>
      <br>
      <div formArrayName="employees">
        <div *ngFor="let emp of employees.controls; let i = index" [formGroupName]="i" class="employee">
          <p>
            <b>Employee : {{i + 1}}</b>
          </p>
          <p>Name :
            <input formControlName="empName">
            <br/>
            <label *ngIf="employees.controls[i].get('empName').errors?.required && isValidFormSubmitted != null && !isValidFormSubmitted"
              class="error">
              Employee name required.
            </label>
          </p>
          <p>Age :
            <input formControlName="age">
            <br/>
            <label *ngIf="employees.controls[i].get('age').errors?.required && isValidFormSubmitted != null && !isValidFormSubmitted"
              class="error">
              Age required.
            </label>
            <label *ngIf="employees.controls[i].get('age').errors?.min && isValidFormSubmitted != null && !isValidFormSubmitted" class="error">
              Minimum age is 21.
            </label>
          </p>
          <p>Skill :
            <select formControlName="skill">
              <option *ngFor="let skill of allSkills | async" [ngValue]="skill.name">
                {{ skill.displayName }}
              </option>
            </select>
            <br/>
            <label *ngIf="employees.controls[i].get('skill').errors?.required && isValidFormSubmitted != null && !isValidFormSubmitted"
              class="error">
              Select skill.
            </label>
          </p>
          <p>
            <button type="button" (click)="deleteEmployee(idx)">Delete</button>
          </p>
        </div>
      </div>
      <button type="button" (click)="addEmployee()">Add More Employee</button>
    </div>
    <br/>
    <button>SUBMIT</button>
    <button type="button" (click)="resetTeamForm()">RESET</button>
  </form>
</div> 
employee.ts
export class Employee {
	empName = null;
	age: number = null;
	skill = null;
} 
team.ts
import { Employee } from './employee';
export class Team {
	teamName: string;
	employees: Employee[];
} 
team-management.service.ts
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { Team } from './team';

const ALL_SKILLS = [
	{ name: 'Java', displayName: 'Java' },
	{ name: 'Angular', displayName: 'Angular' },
	{ name: 'Dot Net', displayName: 'Dot Net' }
];

@Injectable({
	providedIn: 'root'
})
export class TeamManagementService {
	getSkills() {
		return of(ALL_SKILLS);
	}
	saveTeam(team: Team) {
		console.log('------------TEAM------------');
		console.log('Team Name: ' + team.teamName);
		console.log('----- Employee Details -----');
		for (let emp of team.employees) {
			console.log('Emp Name: ' + emp.empName);
			console.log('Emp age: ' + emp.age);
			console.log('Emp Skill: ' + emp.skill);
			console.log('-------------------');
		}
	}
} 
app.component.ts
import { Component } from '@angular/core';
 
@Component({
  selector: 'app-root',
  template: `
           <app-team></app-team>
          `
})
export class AppComponent {
} 
styles.css
.employee {
    border: 2px solid blue;
    width: 275px;
}
.error{
    color: red;
} 
.submitted{
    color: green;
    font-size: 20px;
} 
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { TeamManagementComponent } from './team-management.component';

@NgModule({
      imports: [
            BrowserModule,
            ReactiveFormsModule
      ],
      declarations: [
            AppComponent,
            TeamManagementComponent
      ],
      providers: [],
      bootstrap: [
            AppComponent
      ]
})
export class AppModule { } 








Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...