sarann
sarann

Reputation: 153

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]
     });
  }

Upvotes: 2

Views: 7516

Answers (3)

Naren Murali
Naren Murali

Reputation: 57521

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

Upvotes: 1

Eliseo
Eliseo

Reputation: 57981

export function AtLeastOne(control: AbstractControl) {
  return control.value.find((x:any)=>x)?null:{error:'at least one'}
}

this.formGroup = this.formBuilder.group({
      cb: this.formBuilder.array([],AtLeastOne),
    });

We can show the error in the way

<div style="color: red; padding-top: 0.2rem"
  *ngIf="formGroup.get('cb')?.errors"
>
  At least select one checkbox
</div>

But it's so boried...

Better we can use .css

Imagine you use some like

.error{
  color:red;
  display:none;
}
div[formArrayName].ng-invalid.ng-touched ~.error,
input.ng-invalid.ng-touched ~.error,
form.ng-submitted div[formArrayName].ng-invalid ~.error
{
  display:block;
}

We can use, with the new @for anf @if

@if(formGroup) {
  <form [formGroup]="formGroup">
    <div formArrayName="cb">
      @for(control of cbArray.controls;let i=$index;track i) {
      <label>
        <input
          type="checkbox"
          name="{{ data[i].name }}"
          id="{{ data[i].id }}"
          [formControlName]="i"
        />
        {{ data[i].value }}
      </label>
      }
    </div>
    <div class="error">At least select one checkbox</div>
    <div>
       <button type="submit">Submit</button>
    </div>
  </form>
}

a stackblitz

Upvotes: 1

Vanhorn
Vanhorn

Reputation: 444

When i try the code you posted I get it to work, as down below (note i changed the name from formGroup to myForm:

app.component.ts

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

app.component.html

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

When no checkbox is checked the submit-button is disabled. Or is it something else you cant get to work?

*** Edit Added *ngIf="!myForm?.valid" to error message so it shows and hides depending if you have checkbox checked. You can also listen if the form has been touched before to hide the message initially but i leave that as training for you.

Upvotes: 1

Related Questions