Search code examples
angularangular-validation

Angular custom cross fields validator for Template Driven Form


In a template driven form I have to check if at least one checkbox is checked. I created a custom validator.

HTML form:

<div class="form-group">
  <div class="form-check con-errori">
    <label for="conErrori" class="form-check-label">
      <input type="checkbox" name="conErrori" #conErrori="ngModel" [(ngModel)]="conErroriInput"
       id="conErrori" class="form-check-input" data-either-validator="form-check-input" data-either-group-container-selector=".form-group">Con errori
    </label>
  </div>

  <div class="form-check senza-errori">
    <label for="senzaErrori" class="form-check-label">
      <input type="checkbox" name="senzaErrori" #senzaErrori="ngModel" [(ngModel)]="senzaErroriInput"
       id="senzaErrori" class="form-check-input" data-either-validator="form-check-input" data-either-group-container-selector=".form-group">Senza errori
    </label>
  </div>

  <div *ngIf="!conErrori.valid && (conErrori.dirty ||conErrori.touched)" class="error">
    <div *ngIf="conErrori.errors.ev">
    Selezionare almeno una voce
   </div>
  </div>

  <div *ngIf="!senzaErrori.valid && (senzaErrori.dirty ||senzaErrori.touched)" class="error">
    <div *ngIf="senzaErrori.errors.ev">
    Selezionare almeno una voce
   </div>
  </div>

</div>

validator.ts:

import { Directive, Input, ElementRef, Renderer2 } from '@angular/core';
import { FormControl, NG_VALIDATORS, Validator } from '@angular/forms';

@Directive({

  selector: '[data-either-validator]',
  providers: [
    { provide: NG_VALIDATORS, useExisting: EitherValidatorDirective, multi: true }
  ]
})
export class EitherValidatorDirective implements Validator{

  /*classGroup contains the class to identify all the checkboxes to validate*/
  @Input("data-either-validator") classGroup:string;

 /*groupContainerSelector contains the selector to identify the element that 
contains the checkboxex, so I can validate only those checkboxes*/
  @Input("data-either-group-container-selector") groupContainerSelector:string;

  constructor(private elRef: ElementRef, private renderer: Renderer2) { }

  validate(formControlValue: FormControl) {

    
    /*with the following code I find all the checkboxes with a specific class inside a specific html element 
    and I check if at least one is selected*/
    let groupContainer = this.elRef.nativeElement.closest(this.groupContainerSelector);
    let formControlOfSameGroup = groupContainer.querySelectorAll("." + this.classGroup)

    let validationResult:boolean = false;
    formControlOfSameGroup.forEach(element => {
      if(element.checked){
        validationResult = true;
      }
    });

    if(!validationResult){
      return { 'ev': true}
    }
    else{
      return null;
    }
  }

}

My problem is that it's hard to handle the error message, since I might have 10-20 checkboxes and I should handle as many

  <div *ngIf="!senzaErrori.valid && (senzaErrori.dirty ||senzaErrori.touched)" class="error">
        <div *ngIf="senzaErrori.errors.ev">
        Selezionare almeno una voce
       </div>
      </div>

as checkboxes I have.

What's the correct way to handle this problem (I have a template driven form and not a reactive one)?

Is mine a good approach to handle cross fields validation (in a template driven form)?

Thank you


Solution

  • The solution was on the guidelines, I missed it.

        @Directive({
          selector: '[appIdentityRevealed]',
          providers: [{ provide: NG_VALIDATORS, useExisting: IdentityRevealedValidatorDirective, 
                      multi: true }]
        })
        export class IdentityRevealedValidatorDirective implements Validator {
          validate(control: AbstractControl): ValidationErrors {
    
          /*this way you can retrieve the value of the fields you have to validate*/
          const name = control.get('name');
          const alterEgo = control.get('alterEgo');
    
          if(error){
             return { 'errorCode': true}
          }
          else{
               return null;
          }
        }
      }
    

    During cross fields validation the validator should be applied to the form and not to the fields.

    <form #heroForm="ngForm" appIdentityRevealed>  
    
    <div *ngIf="heroForm.errors?.errorCode && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
        Name cannot match alter ego.
    </div>