Search code examples
angulartypescriptangular-formsangular15

How to show validation error message in the angular form


I am trying to show validation error message for angular for. I have three checkboxes. If i not selected anyone i want to show error message. How to do it in reactive form validation in angular.

Demo: https://stackblitz.com/edit/angular-gitter-ddqhne?file=app%2Fapp.component.html

app.component.html:

    <form [formGroup]="formGroup">
    <label *ngFor="let val of data"><input type="checkbox" name="val.name" id="val.id" formControlName="cb"> {{val.value}}</label>
    <div style="color: red; padding-top: 0.2rem">
        Atleast select one checkbox
    </div>
    <hr>
    <div>
        <button type="submit">Submit</button>
    </div>
    </form>

app.component.ts:

  ngOnInit(): void {
     this.formGroup = this.formBuilder.group({
        cb: [false, Validators.requiredTrue]
     });
  }

Solution

  • Updated my answer to use formControlName the remaining explanation stays the same! But I use .bind(this, data) to pass the data that is used on the *ngFor to initialize the checkboxes, because the data is dynamic, so we need to pass the original data to ensure the values are checked so that atleast once checkbox is checked!

    validator

    export function ValidateCheckboxes(data: any, control: AbstractControl) {
      console.log(data, control.value.cb);
      if (
        !data.some(
          (item: any, index: number) => control.value.cb[index][item.id]
        )
      ) {
        return { checkboxSectionValid: true };
      }
      return null;
    }
    

    stackblitz demo


    We can use a formArray for the checkboxes this will ensure the inputs are under the same formArray control

    this.formGroup = this.formBuilder.group({
      cb: this.formBuilder.array([]),
    });
    const cb: FormArray = this.cbArray;
    this.data.forEach((item: any) => {
      cb.push(new FormControl(null));
    });
    

    We need a custom validator to check if any of the checkboxes are checked, we use the below code for that

    this.formGroup.setValidators(ValidateCheckboxes);
    

    validator function

    export function ValidateCheckboxes(control: AbstractControl) {
      console.log(control.value.cb);
      if (!control.value.cb.some((item: any) => item)) {
        return { checkboxSectionValid: true };
      }
      return null;
    }
    

    On the html side, we group all the checkboxes under one formGroupName, then assign the formArray controls to the checkboxes. Then we can check if the error exists by doing *ngIf="formGroup?.errors?.checkboxSectionValid" on the error message div!

    <div formArrayName="cb">
      <label *ngFor="let val of cbArray.controls; let i = index"
        ><input
          type="checkbox"
          name="{{ val.name }}"
          id="{{ val.id }}"
          [formControl]="val"
        />
        {{ data[i].value }}</label
      >
    </div>
    <div
      style="color: red; padding-top: 0.2rem"
      *ngIf="formGroup?.errors?.checkboxSectionValid"
    >
      Atleast select one checkbox
    </div>
    

    full code

    import { Component, OnInit, VERSION } from '@angular/core';
    import {
      AbstractControl,
      FormBuilder,
      FormGroup,
      FormArray,
      FormControl,
    } from '@angular/forms';
    
    export function ValidateCheckboxes(control: AbstractControl) {
      console.log(control.value.cb);
      if (!control.value.cb.some((item: any) => item)) {
        return { checkboxSectionValid: true };
      }
      return null;
    }
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
    })
    export class AppComponent implements OnInit {
      formGroup: FormGroup;
      public data = [
        {
          name: 'chk1',
          id: 'chk1',
          value: 'Car',
        },
        {
          name: 'chk2',
          id: 'chk2',
          value: 'Bus',
        },
        {
          name: 'chk3',
          id: 'chk4',
          value: 'Motor',
        },
      ];
    
      constructor(private readonly formBuilder: FormBuilder) {}
    
      get cbArray() {
        return this.formGroup.get('cb') as FormArray;
      }
    
      ngOnInit(): void {
        this.formGroup = this.formBuilder.group({
          cb: this.formBuilder.array([]),
        });
        const cb: FormArray = this.cbArray;
        this.data.forEach((item: any) => {
          cb.push(new FormControl(null));
        });
        this.formGroup.setValidators(ValidateCheckboxes);
      }
    }
    

    html

    <div style="padding: 1rem">
      <form [formGroup]="formGroup">
        <div formArrayName="cb">
          <label *ngFor="let val of cbArray.controls; let i = index"
            ><input
              type="checkbox"
              name="{{ val.name }}"
              id="{{ val.id }}"
              [formControl]="val"
            />
            {{ data[i].value }}</label
          >
        </div>
        <div
          style="color: red; padding-top: 0.2rem"
          *ngIf="formGroup?.errors?.checkboxSectionValid"
        >
          Atleast select one checkbox
        </div>
        <hr />
        <div>
          <button type="submit">Submit</button>
        </div>
      </form>
    </div>
    

    stackblitz demo