0

I have a form which contains a set of checkboxes and I want to create a custom validator that forces to check at least one of them.

In form.component.ts:


  form: FormGroup = new FormGroup({
    customer: new FormControl('', [Validators.required]),
    partTimeidentification: new FormControl('', [Validators.required]),
    inspectionType: new FormGroup({
      ocr: new FormControl(false, []),
      anomalyDetection: new FormControl(false, []),
      metrics: new FormControl(false, []),
      wiresInspection: new FormControl(false, []),
      other: new FormControl(false, []),
    }, { validators: atLeastOneValidator() }),
    cameraSettings: new FormGroup({
      model: new FormControl('', [Validators.required]),
      size: new FormControl('', [Validators.required]),
      speedOb: new FormControl('', [Validators.required]),
      flashColor: new FormControl('', [Validators.required]),
    }),
  });

In form.component.html (I omitted fields to keep it as clear as possible, you have the structure up here):

 <form class="flex-col text-center" [formGroup]="form!">
     (more fields here)
     <app-box [title]="'Inspection type'" class="text-center me-2" [fit_height]="true" formGroupName="inspectionType">
      <div class="form-check">
       <input class="form-check-input" type="checkbox" formControlName="ocr" name="ocr" value="ocr"/>
       <label class="form-check-label"> OCR </label>
      </div>
      (more checkboxes)
    </app-box>
    (more fields)
    <button [disabled]="!form!.valid" type="button" class="btn bg-primary-tb text-white mt-3 col-4" (click)="submit()">Continue </button>
 </form>

atLeastOneValidator.directive.ts:

import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms";

export function atLeastOneValidator(): ValidatorFn {
    return (group: AbstractControl): ValidationErrors | null => {
                let controls = group.parent.controls;
                let theOne = Object.keys(controls).findIndex(key => controls[key].value !== false);
      
                if (theOne) return null;
              
              return { 'atLeastOneRequired': 'At least one should be selected'}
        };

      
};
  

Nevertheless, I keep receiving the error message error TS2531: Object is possibly 'null'. in the validator's group.parent.

To put the validator's code into words: I want to access the parent (what I guess it is inspectionType, a FormGroup) and then look if any of the children (ocr, anomalyDetection... any of those FormControls) are set to true, in that case the form is valid.

Kalima
  • 11
  • 2
  • 6
  • Does this answer your question? [How to suppress "error TS2533: Object is possibly 'null' or 'undefined'"?](https://stackoverflow.com/questions/40349987/how-to-suppress-error-ts2533-object-is-possibly-null-or-undefined) – Yong Shun Jun 13 '22 at 11:44
  • @YongShun No, I tried that already and as the title says, it suppresses the error but it's still there, it is actually null as it warned. My app manages to compile but it's completely broken, I get this in the navigator ERROR TypeError: Cannot read properties of null (reading 'controls') – Kalima Jun 13 '22 at 11:48
  • The validator is added to the FormGroup instead of a control, the naming in the function `group: AbstractControl` is confusing, what are you expecting, a control or a group? – John Zwarthoed Jun 13 '22 at 12:01
  • both `FormControl` and `FromGroup` inherits from `AbstractControl` which is why it may be fine to use `AbstractControl` rather than any of the more specific types if your validator only utilizes some of the general functions provided by `AbstractControl` – Mikkel Christensen Jun 13 '22 at 12:10
  • let controls = group?.parent?.controls || {}; might fix it? – MikeOne Jun 13 '22 at 13:28

1 Answers1

1

Create a validator which expects a FormGroup and then append the validator to the FormGroup which is the parent to the FormControl of which at least one must be true.

// validator
function atLeastOneValidator() {
  return (control: FormGroup) => {
    const raw = control.getRawValue();
    return Object.keys(raw).map(k => raw[k]).some(_ => _) ? null : {check_one: "Must check at least one"}
  }
}
// form group
  fg: FormGroup;
  constructor(private fb: FormBuilder) {
    this.fg = this.fb.group({
      a: this.fb.control(false),
      b: this.fb.control(false),
      c: this.fb.control(false),
    }, {validators: [atLeastOneValidator()]});
  }

minimal stackblitz: https://stackblitz.com/edit/angular-ivy-f3c3e1?file=src/app/app.component.ts

Mikkel Christensen
  • 2,502
  • 1
  • 13
  • 22