2

I have created an Angular reactive form which has a couple of basic form control elements, then a form array of simple form groups. You can dynamically add & remove form group instances from the form array.

Here is a demo.

When you dynamically add a new form group instance, its fields are required. However, you can remove it. Since the fields are required, the form is invalid immediately after your add a new form group. The problem that I am having is that the form remains invalid even if you remove the form group instance and every remaining form control is valid. I've checked this - I iterated through all my form elements and each individual form control is valid, but the form array remains invalid.

How can I ensure the form array is valid after I remove an invalid form group from it?

Component

import { AbstractControl, FormArray, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';

import {Component, OnInit } from '@angular/core';

/**
 * @title Inputs in a form
 */
@Component({
  selector: 'input-form-example',
  templateUrl: 'input-form-example.html',
  styleUrls: ['input-form-example.css'],
})
export class InputFormExample implements OnInit {

  studyGuideForm: FormGroup;
  error: string = null;

  constructor(
    private fb: FormBuilder
    ) { }

    ngOnInit() {
      this.studyGuideForm = this.fb.group({
        studyGuideName: ['', Validators.required],
        description: [''],
        flashCards: this.fb.array([
          this.fb.group({
            front: ['', Validators.required],
            back: ['', Validators.required]
          })
        ], this.invalidFlashCardValidator())
      });
  }

  onSubmit() {
    if (this.studyGuideForm.valid) {
      console.log("Validation successful, can create");
      this.error = null;
    } else {
      console.log("The form is invalid, cannot submit");
      this.error = "Please enter all required fields and try again.";
    }
  }

  get flashCards() {
    return this.studyGuideForm.get('flashCards') as FormArray;
  }


  addFlashCard() {
    this.flashCards.push(this.fb.group({ front: '', back: ''}));
  }

  isRemovable() {
    return this.flashCards.length > 1;
  }

  removeFlashCard(pos: number) {
    this.flashCards.controls.splice(pos, 1);
  }

  invalidFlashCardValidator(): ValidatorFn {
    return (control: AbstractControl): {[key: string]: any} | null => {
      if (this.studyGuideForm) {
        let flashCardControls = this.flashCards.controls;
        for (const control in flashCardControls) {
          let fg = flashCardControls[control] as FormGroup;
          if (!fg.valid) {
            return {'invalidFlashCard': { value: 'Invalid flash card detected'}};
          }
        }
      }
      return null;
    }
  }

}

Template

<mat-card class="card-container">
    <h1 class="center-text">Create Study Guide</h1>
    <form [formGroup]="studyGuideForm" class="form-primary">
        <h2>Basic Information</h2>
        <mat-form-field class="full-width">
          <input matInput placeholder="Name" formControlName="studyGuideName" name="name" required>
        </mat-form-field>
        <mat-form-field class="full-width">
          <textarea matInput placeholder="Description" formControlName="description" name="description"></textarea>
        </mat-form-field>
        <div formArrayName="flashCards">
            <h2>Flash Cards</h2>
            <div *ngFor="let flashCard of flashCards.controls; index as i; first as isFirst" class="flash-card full-width" appearance="outline">
                <div [formGroupName]="i">
                    <div *ngIf="isRemovable()" class="close" (click)="removeFlashCard(i)">X</div>
                    <mat-form-field class="full-width">
                        <textarea matInput placeholder="Front" formControlName="front" name="front{{i}}" required></textarea>
                    </mat-form-field>
                    <mat-form-field class="full-width">
                        <textarea matInput placeholder="Back" formControlName="back" name="back{{i}}" (keydown.Tab)="onTab(i)" required></textarea>
                    </mat-form-field>
                </div>
            </div>
            <button mat-fab color="accent" (click)="addFlashCard()" class="add-button">+</button>
        </div>
    </form>
</mat-card>
<div class="button-container">
    <button mat-raised-button color="primary" class="big-button" (click)="onSubmit()">Submit</button>    
</div>
<p class="error-text" *ngIf="error || (error && !studyGuideForm.valid)">{{ error }} </p>

1 Answer 1

3

just update validity state of form array manually using updateValueAndValidity method of AbstractControl

  removeFlashCard(pos: number) {
    this.flashCards.controls.splice(pos, 1);
    this.flashCards.updateValueAndValidity();
  }

based on @Eliseo's comment, better approach would be to use removeAt() method of FormArray

  removeFlashCard(pos: number) {
    this.flashCards.removeAt(pos);
  }
Sign up to request clarification or add additional context in comments.

3 Comments

ysf, USE addControl and removeControl to add/remove control, see the docs: angular.io/api/forms/FormGroup
@Eliseo thank you for the tip. i updated my answer to utilize removeAt() from FormArray since he is adding/removing FormGroups to/from FormArray
Perfect! Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.