Reputation: 153
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
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;
}
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>
Upvotes: 1
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>
}
Upvotes: 1
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