Tips for displaying validation error messages in an Angular form

I need help displaying a validation error message for an Angular form. I have three checkboxes and I want to show an error message if none of them are selected. Can anyone provide guidance on how to implement reactive form validation in Angular?

Here is a 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">
        At least 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]
     });
  }

Answer №1

Just made an update to my solution by implementing the formControlName. The explanation remains unchanged! However, I utilized .bind(this, data) to transmit the dynamic data used in the *ngFor to initialize the checkboxes. This is necessary to ensure that the values are checked, guaranteeing at least one checkbox is selected!

Validation function

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

Check out the stackblitz demo


To simplify managing the checkboxes, we can utilize a formArray to keep all inputs 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));
});

A custom validator is required to verify if any of the checkboxes are ticked off. Here's how you can set it up:

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

In your HTML, group all checkboxes within a single formGroupName, then attach the formArray controls to each checkbox. To display an error message for unchecked boxes, use

*ngIf="formGroup?.errors?.checkboxSectionValid"
.

<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>

Complete code snippet

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 markup

<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"
        /&ft;
        {{ 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>

Preview the stackblitz demo

Answer №2

After testing the code you provided, I was able to successfully make it work with some modifications. Below is the updated version where 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">
      At least select one checkbox
    </div>
    <hr />
    <div>
        <button type="submit" [disabled]="!myForm?.valid">Submit</button>
    </div>
  </form>
</div>

If no checkbox is checked, the submit button will be disabled. Is there anything else that you are struggling to get to function properly?

*** Edit I also included *ngIf="!myForm?.valid" in the error message to show or hide it based on whether a checkbox is selected. You can also set it to only display after the form has been touched initially, but I'll let you tackle that part as a learning exercise.

Answer №3

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

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

We can display the error message like this

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

However, there is a more visually appealing way to handle this using CSS

Imagine utilizing something like this:

.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 the new @for and @if directives for even more flexibility

@if(formGroup) {
  <form [formGroup]="formGroup">
    <div formArrayName="checkboxGroup">
      @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">Please select at least one checkbox</div>
    <div>
       <button type="submit">Submit</button>
    </div>
  </form>
}

Here is a StackBlitz example

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

This error occurs when trying to assign a value to a property of a variable that is currently undefined

Having some issues with assigning the latitude and longitude values to a variable in my code. I am able to retrieve them correctly, but when trying to use them in another method (onUpload()), I am facing some errors. export class latlonComponent implement ...

When viewing a React data table in Chromium browsers, the columns on the right side may flicker when the screen is small or the browser

I recently integrated the React data grid Npm package by adazzle. You can find more information about it here. I encountered an issue which you can see in this example: https://codesandbox.io/s/react-data-grid-example-9sb93?file=/src/App.tsx When using a ...

Return true for cucumber datatable in typescript without fail

I am facing an issue where the following step definition always returns true even for incorrect data from the dataTable. Can someone assist me in correcting the syntax in TypeScript with Chai assertions? Then(/^Verify the following details in report$/, a ...

Function input custom operator in RxJs

I am currently working on developing a custom rxjs operator. My previous custom operators, such as MonoTypeOperatorFunction or the regular Observable that accepts input like strings or numbers, have been successful. However, I am facing a challenge with cr ...

Exploring NextJS with Typescript

Struggling to incorporate Typescript with NextJS has been a challenge, especially when it comes to destructured parameters in getInitialProps and defining the type of page functions. Take for example my _app.tsx: import { ThemeProvider } from 'styled ...

Conclude using flatMap within Angular

In my Angular 5 project, I am using RxJS to make two service calls where one is dependent on the result of the other. To achieve this, I am utilizing the flatMap function. Additionally, I need to perform some operation once both API calls have been complet ...

Combining Angular 4 with a pre-existing Spring Boot web app utilizing JSP components

Currently, I have a live Spring Boot web application that uses jsp and JavaScript. My goal is to gradually update existing pages with Angular when time allows. While I am new to Angular, most of the information I have come across suggests that it require ...

Why hasn't the variable been defined?

Why am I receiving an error message saying "test is not defined" in this code? Even though I have properly defined the variable in another service file, it seems to be causing issues here. Any insights on what could be going wrong? import { Injectable } f ...

Utilizing personalized Angular component as a feature within OpenLayer map

I am exploring the possibility of integrating my own component as a zoom control for an OpenLayers map. I came across some helpful information here, indicating that it can be achieved by creating specific HTML elements. However, I already have a pre-existi ...

What is the best way to showcase a firebase "row" containing two columns within an Ionic 2 application?

Currently in the process of developing an app to keep track of assignments using Ionic 2/Typescript and Firebase as the backend database. The main page displays a list of assignments retrieved from the database. Creating a new assignment requires the user ...

Utilizing *ngIf for Showing Elements Once Data is Completely Loaded

While working on my Angular 2 app, I encountered an issue with the pagination UI loading before the data arrives. This causes a visual glitch where the pagination components initially appear at the top of the page and then shift to the bottom once the data ...

How can I activate a function in one Angular 2 component from another?

I am working with two components named componentA and componentB. They are both siblings and children of componentMother. My goal is to have a button click on componentA trigger a function call on componentB. Would the best approach be using a service wi ...

React component not displaying any content due to ternary operator condition being met with variable equal to 0

Seeking to display a React component upon clicking another component. When clicked, I assign the eventKey of the component to a savedId in order to render the corresponding data from the array at that index. Click Action <ListGroup> {data.map((it ...

Nested Angular click events triggering within each other

In my page layout, I have set up the following configuration. https://i.stack.imgur.com/t7Mx4.png When I select the main box of a division, it becomes highlighted, and the related department and teams are updated in the tabs on the right. However, I also ...

Angular 1.5 Karma unit test causes duplicate loading of ng-mock library

My current web app is built using Typescript 2.4.2 and compiled with the latest Webpack version (2.7.0). I am in the process of incorporating Karma tests utilizing Jasmine as the assertion library. Below is my karma configuration file: 'use strict& ...

Issue encountered with Azure DevOps during TypeScript (TS) build due to a type mismatch error: 'false' being unable to be assigned to type 'Date'. Conversely, the build functions correctly when run locally, despite the type being defined as 'Date | boolean'

I am facing an issue with my NestJS API while trying to build it using Azure DevOps pipeline. The build fails with the following error: src/auth/auth.controller.ts(49,7): error TS2322: Type 'false' is not assignable to type 'Date'. src/ ...

Creating a TypeScript schema with nested maps and arrays using Dynamoose

I'm currently in the process of developing a schema for a specific example: { "foods": [ { "fruits": [{ "apple": { "color": "red", ...

Retrieving the value from a string Enum in Angular based on an integer

export enum RoleTypesEnum { RoleA = 'Role is A', RoleB = 'Role is B', } // in TypeScript file public RoleTypesEnum = RoleTypesEnum; I am trying to obtain the string value (e.g. Role is B) from an enum using an integer. If I u ...

The anticipated outcomes are not achieved when utilizing environmental variables in Angular 2

Is it expected that when we use ng serve --env=prod, it should work with the values set in environment.prod.ts? Well, in my experience, it doesn't seem to be working as expected as I always receive the values from environment.ts (which is the developm ...

Is there a way to implement the post method in ng2-smart-table whenever a new row is inserted?

Incorporating the ng2 smart table library into my angular 2 project has been helpful. I am now faced with a requirement to trigger an API request using the http.post() method whenever a new row is added to the table. Is there a way for me to retrieve the ...