Sunday, April 7, 2024

FormArray in Angular With Example

FormArray in Angular provides another way to group any number of controls apart from FormGroup in Angular. Difference between FormGroup and FormArray is that in FormGroup each child FormControl is added as the part of FormGroup object as a key, value pair where control name is the key. In FormArray value of each child FormControl is added into an array.

Since FormArray internally maintains an array of FormControls so it is easy to add or remove controls from it giving you capability to create a dynamic form using FormArray in Angular.

Dynamic Form using FormArray Angular example

In this FormArray example we’ll create a form where user has an option to add input text boxes or remove them dynamically.

FormArray in Angular

Data Model (member.model.ts)

Member class defines the data model reflected in the form.

export class Member {
  name: string;
  mail: string;
  exercises: string[];
  constructor(name: string, mail: string, exercises: string[]) {
    this.name = name;
    this.mail = mail;
    this.exercises = exercises;
  }
}

CSS for Validation

In the form we show green bars at the left side of the controls that are required. That bar changes to red if value not entered for the required field or value is invalid and an error message is also displayed. Following CSS shows the green and red bars.

src/assets/forms.css

.ng-invalid.ng-untouched:not(form):not(div)  {
  border-left: 5px solid #42A948; /* green */
}
  
.ng-invalid.ng-touched:not(form):not(div)  {
  border-left: 5px solid #a94442; /* red */
}

.ng-invalid.ng-untouched:not(form):not(div) means select all elements having both ng-invalid and ng-untouched set within its class attribute and it is not at form or div level. If you won’t add not(form) you will have a bar at the form level too encompassing all controls and not having at div level ensures that you don’t have a bar at the whole group level.

In the index.html file, update the <head> tag to include the new style sheet.

<link rel="stylesheet" href="assets/forms.css">

Component Class (app.component.ts)

import { Component, OnInit} from '@angular/core';
import { FormArray, FormBuilder, FormGroup, Validators} from '@angular/forms';
import { Member } from './member.model';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html', 
})
export class AppComponent implements OnInit {
  constructor(private fb: FormBuilder) {}
  member = new Member('', '', ['']);
  submitted = false;
  membershipForm!: FormGroup;
  ngOnInit() {
    this.membershipForm = this.fb.group({
      memberName: ['', [Validators.required, Validators.minLength(5)]],
      email: ['', [Validators.required, Validators.email]],
      exercises: this.fb.array([this.createElement()])
    });
  }
  createElement(){
    return this.fb.control('', Validators.required);
  }
  addExercise() {
    this.exercises.push(this.createElement());
  }
  get exercises() {
    return this.membershipForm.get('exercises') as FormArray;
  }
  deleteControl(i: number){
    if(this.exercises.length !== 1){
      this.exercises.removeAt(i);
    }
  }

  onSubmit(){
    this.submitted = true;
    this.member.name = this.membershipForm.value.memberName;
    this.member.mail = this.membershipForm.value.email;
    this.member.exercises = this.membershipForm.value.exercises;
  }
}

Important points to note here are-

  1. In this ReactiveForm, FormBuilder is used to build the form so FormBuilder is imported.
  2. For using FormArray you will have to import FormArray from Angular Forms module.
    	import { FormArray, FormBuilder, FormGroup, Validators} from '@angular/forms';
    	
  3. exercises FormArray is created here using the FormBuilder.array() method that defines an array.
    	exercises: this.fb.array([this.createElement()])
    	
  4. FormArray can be initialized with zero or more controls. Here it is initialized with a single FormControl.
  5. createElement() method is used to return a FormControl instance, Validators.required argument ensures that value has to be entered for the control.
    createElement(){
      return this.fb.control('', Validators.required);
    }
    
  6. There is also a getter method exercises() returning the exercises FormArray. It is a convenience method to get easy access to the form array instance otherwise you will have to repeat the membershipForm.get('exercises') method each time.
  7. addExercise() method is used to push a new FromControl instance to the FormArray.
  8. deleteControl is used to remove control from the given index. Uses the removeAt() method of the FormArray which removes the control at the given index in the array. We want to ensure that atleast one exercise is added that is why a check for length.
    deleteControl(i: number){
      if(this.exercises.length !== 1){
        this.exercises.removeAt(i);
      }
    }
    
  9. When the form is submitted you create a Member instance using the entered values.

Template (app.component.html)

You can bind the exercises form array to the template using formArrayName directive.

<div class="container">
  <h2>Gym Membership Form</h2>
  <form [formGroup]="membershipForm" (ngSubmit)="onSubmit()">           
    <div class="row mb-3">
      <div class="col-sm-8">
        <label for="name">Name</label>
        <input type="text" class="form-control" id="name"
              formControlName="memberName">   
        <div class="alert alert-danger" *ngIf="membershipForm.get('memberName')?.invalid 
        && membershipForm.get('memberName')?.touched">
          <div *ngIf="membershipForm.get('memberName')?.errors?.['required']">
            Name is required.
          </div>
          <div *ngIf="membershipForm.get('memberName')?.errors?.['minlength']">
            Name must be at least 5 characters long.
          </div>  
        </div>   
      </div>                
    </div>
    <div class="row mb-3">
      <div class="col-sm-8">
        <label for="email">email</label>
        <input type="email" class="form-control" id="email"
          formControlName="email">  
          <div class="alert alert-danger" *ngIf="membershipForm.get('email')?.invalid 
            && membershipForm.get('email')?.touched">
            Please enter a valid email
          </div>
      </div>                       
    </div>
    <div class="row mb-3">
      <div class="col-sm-8">
    <div formArrayName="exercises">
      <h4>Add favorite exercises</h4>
      <button class="btn btn-primary mb-2" type="button" (click)="addExercise()">Add Exercise</button>
      <table class="table">
        <tr 
          *ngFor="let exercise of exercises.controls; let i = index;">
          <td><input type="text" class="form-control" [formControlName]="i"></td>             
          <td><button class="btn btn-danger" type="button" (click)="deleteControl(i)">Delete</button></td>
          <td class="alert alert-danger" *ngIf="exercise.invalid 
            && exercise.touched">
            Please enter value
          </td>
        </tr>            
      </table>
    </div>
    </div>
    </div>
  <button type="submit" [disabled]="membershipForm.invalid" class="btn btn-success">Submit</button>
  </form> 
</div>
<hr>
<div *ngIf="submitted">
  <div class="row">
    <div class="col-sm-8">
      <p>Name: {{member.name}}</p>
      <p>email: {{member.mail}}</p>
      <p>Favorite Exercises: {{member.exercises}}</p>
    </div>
  </div>
</div> 

Important points to note here are-

  1. FormArray instance is bound to the template using formArrayName
    <div formArrayName="exercises">
    
  2. Using *ngFor directive you can iterate over each form control instance stored in the form array instance.
    <tr class="form-group" 
                  *ngFor="let exercise of exercises.controls; let i = index;">
    
  3. Because form array elements are unnamed, you assign the index to the i variable and pass it to each control to bind it to the formControlName input.
    <input type="text" class="form-control" [formControlName]="i">
    
  4. Add exercise button gives you the option to dynamically add controls to your form.
  5. Delete button gives you the option to dynamically remove controls from your form.
  6. There is code for validation too. Please refer Angular Reactive Form Validation Example to know more about validation.
  7. On submitting the form the entered values are shown on the form.
    <div *ngIf="submitted">
      <div class="row">
        <div class="col-sm-8">
          <p>Name: {{member.name}}</p>
          <p>email: {{member.mail}}</p>
          <p>Favorite Exercises: {{member.exercises}}</p>
        </div>
      </div>
    </div>  
    

That's all for this topic FormArray in Angular With Example. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Angular Tutorial Page


Related Topics

  1. FormBuilder in Angular Example
  2. Angular Template-Driven Form Validation Example
  3. Angular Form setValue() and patchValue()
  4. Radio Button in Angular Form Example
  5. Checkbox in Angular Form Example

You may also like-

  1. Angular @Input and @Output Example
  2. Angular Disable Button Example
  3. CanDeactivate Guard in Angular With Example
  4. Angular Event Binding With Examples
  5. Java ReentrantReadWriteLock With Examples
  6. isAlive() And join() Methods in Java Multi-Threading
  7. Create Date Object in Java With Date and Time Values
  8. How to Read Properties File in Spring Framework

No comments:

Post a Comment