BondAddict
BondAddict

Reputation: 790

Angular 11 Custom Validation

I've been looking all over trying to get custom validation to work in angular 11. So far I have been unable to get it to work as expected.

If I use the direct method from Angular.io (Below) then the param (called 'control') is undefined.

 export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): {[key: string]: any} | null => {
    const forbidden = nameRe.test(control.value);
    return forbidden ? {forbiddenName: {value: control.value}} : null;
  };
}

I can get this to at least show a control object but the value is always an empty string

export function requiredIfTrue(control: AbstractControl) {
  const value = control.value;

  if (!value) {
      return null;
  }

  return value === '1' ? {requiredIfTrue:true}: null;
}

Hoping someone can help.

I'll include my html and ts as well below.

HTML:

<form [formGroup]="fg">
<mat-grid-list cols="2" rowHeight="100px">
  <mat-grid-tile>
    <mat-form-field appearance="fill">
      <mat-label>Type</mat-label>
      <mat-select #regType formControlName="typeControl">
        <mat-option *ngFor="let type of types" [value]="type">
          {{type}}
        </mat-option>
      </mat-select>
    </mat-form-field>
  </mat-grid-tile>
  <mat-grid-tile>
    <mat-form-field appearance="fill">
      <mat-label>First Name</mat-label>
      <input matInput formControlName="fNameControl" type="text" [maxlength]="charLimit4">
    </mat-form-field>
  </mat-grid-tile>
</mat-grid-list>

TS:

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import {requiredIfTrue} from './custom-validators/custom-validators';

@Component({
  selector: 'app-registration-form',
  templateUrl: './registration-form.component.html',
  styleUrls: ['./registration-form.component.css']
})
export class RegistrationFormComponent implements OnInit {

  constructor() { }


  fg: any;
  types: any = ['1','2'];

  ngOnInit(): void {
    this.fg = new FormGroup({
      typeControl: new FormControl('',[Validators.required]),
      fNameControl: new FormControl('',[requiredIfTrue]),
    });
  }
Submit(): any{
  debugger;
};
}

TLDR: A field should be required if a certain value is selected from a ddl. Currently all methods I can find online for angular 11 have not worked. Please help.

UPDATE: This works, but I would like to validate the individual controls, not the form group. That way the functions doesn't get hit even on controls I dont care about.

Working Validator

Thank you in advance!

Upvotes: 1

Views: 595

Answers (1)

David Kidwell
David Kidwell

Reputation: 720

If I am understanding correctly, you only want the second control to be required if there is a specific value in your other control, the dropdown list.

There are a couple ways to do this,

  1. You can dynamically add and remove the required validator from that control,

     this.fg.controls.typeControl.valueChanges.subscribe(value => {
     if(value === 'whatever'){ 
         this.fg.controls.fNameControl.setValidators([Validators.required]);
     } else { 
         this.fg.controls.fNameControl.clearValidators();
     }  );
    

This is listening for changes on the dropdown list and then adding and removing the required validator from the other control depending on the value of the dropdown.

  1. You can use a custom validator like you are trying, but it will be a 'cross-field' validator. Meaning that the validity of one field is based on the value of another.

The angular docs give a good explanation of how to do this here, https://angular.io/guide/form-validation#cross-field-validation

The key is that you need to apply the validator to the FormGroup containing your two controls so that the AbstractControl inside the validator contains both FormControls and use the value of one (your dropdown) to validate the value of the other (fNameControl), rather than to an individual FormControl.

  this.fg = new FormGroup({
    typeControl: new FormControl('',[Validators.required]),
    fNameControl: new FormControl(''),
  }, {validators: YourCustomCrossFieldValidator });

The validator would look something like,

    export const YourCustomCrossFieldValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
 //control is a reference to the FormGroup
  const typeControl = control.get('typeControl');
  const fNameControl = control.get('fNameControl');

  return typeControl && fNameControl && typeControl.value === 'whatever' && (!fNameControl.value || fNameControl.value.length === 0) ? { requiredIfTrue: true } : null;
};

EDIT: One thing I forgot to mention is that when you add a validator the FormGroup, then by default the error will be applied to that FormGroup and not to any of the individual controls.

One way to solve this is to manually add an error to the control inside the validator and then always return null (so that we don't duplicate the error on the FormGroup).

fNameControl.setErrors({...fNameControl.errors, 'requiredIfTrue': {value: true}});
return null;

Upvotes: 1

Related Questions