50

I'm using Material 2 in my app, but in this question I want to solve a problem specifically with Input.

As you can see in API Reference there's a property bind called required, which shows as asterisk in the placeholder.

So, I'm wondering if there's a way to check if the form control has an specific validator in Angular, because I really don't want to set manually for each input [required]="true/false"

I read the AbstractControl docs and I didn't find anything about it. I've encountered the hasError method (which ironically isn't documented in nowhere ... neither in FormGroup nor in FormControl nor in AbstractControl), however this is not what I'm looking for. It just checks if the form control has the error, but as you may have read, I want to check if the control has some specific validators...

Some code:

<md-input-container>
  <input placeholder="Placeholder" 
         mdInput [formControl]="anyCtrl" 
         [required]="anyCtrl.hasValidator('required')"> <!-- something like this -->
</md-input-container>

I hope the question is clear enough. Thanks in advance.

Collin Barrett
  • 2,441
  • 5
  • 32
  • 53
dev_054
  • 3,448
  • 8
  • 29
  • 56

11 Answers11

51

EDIT: This answer was for older Angular versions. See pasek's answer below for a much more succinct approach in Angular v12+.

Angular doesn't really provide a great, clean way to do this, but it is possible. I think the validators are stored in a service that is injected into the FormBuilder(NG_VALIDATORS), and I'm going to look into hijacking that service or injecting it into a component, but for now this will work:

The docs and the source show a validator member on AbstractControl typed to ValidatorFn. ValidatorFn unfortunately simply has a null typing, so we can't see what's going on. However, after poking through the generated source and probing an app, it seems we can pass this validators method a control parameter, which will return an object of all validators present on that control, regardless of whether or not it's passing.

Strangely, this only works on the FormControl itself and not the FormGroup (on the FormGroup, the validators member is not a function and was always null in my testing). The compiled JS says this function takes a control parameter; I've tried passing in FormControl references but as far as I can tell it will just return the validators on the control as long as this parameter is not null.

Getting validators on a FormControl

// in the constructor
this.myForm = this.formBuilder.group({
  'anyCtrl': ['', Validators.required],
  'anotherCtrl': ['', Validators.compose([Validators.required, Validators.email])]
});

// later on 
let theValidators = this.myForm.controls['anyCtrl'].validator('');
console.log(theValidators) // -> {required: true};

let otherValidators = this.myForm.controls['anotherCtrl'].validator('');
console.log(otherValidators); // -> {required: true, email: true}

Making it easier to grab:

public hasValidator(control: string, validator: string): boolean {
  return !!this.myForm.controls[control].validator(control).hasOwnProperty(validator);
 // returns true if control has the validator
}

and in your markup:

<md-input-container>
  <input placeholder="Placeholder" 
         mdInput [formControl]="anyCtrl" 
         [required]="hasValidator('anyCtrl', 'email')">
</md-input-container>

Special case for Validators.required

The required validator has a shortcut. The [required] binding is actually an instance of the RequiredValidator directive (line 5022 of source/forms.js). This directive actually will add the required Validator to the FormControl it's on. It's equivalent to adding Validators.required to the FormGroup upon initialization. So, setting the bound property to false will remove the required Validator from that control and vice versa...either way, the directive affects the FormControl.required value, so binding it to a property that it changes won't really do much.

The only difference is that the [required] directive adds the asterisk to the placeholder while Validators.required does not.

I'm going to keep looking into NG_VALIDATORS, but I hope this helps for now!

joh04667
  • 7,159
  • 27
  • 34
  • 2
    Hello, @joh04667, First, I just want to say: **thanks**. I just tested your solution [**here**](https://plnkr.co/edit/d2nprY4KTf4P9V3sIwrA) and it seems to work. However, It's worth to mention that it throws errors If you have a `control` that has no validators, in other words, `validators` is *null*. Test it [**here**](https://plnkr.co/edit/wsv1wyDOMU57vBpu8V6s). That said, thanks more one time. – dev_054 May 18 '17 at 00:04
  • 3
    Ahh, and about your last statement: I know that's the **difference** is just the *asterisk* and that's why I want it :) I hope one day the Material 2 Team implements a solution to add asterisk looking for reactive forms, without the need of set the `required` in HTML itself. :) – dev_054 May 18 '17 at 00:05
  • 4
    AFAIK the `hasValidator()` method only works for validators that 'fail' (ie. a required validator on a control with value '' (emtpy)). Since if they pass they return null. A way around this would be to set the value so that it always fails and restore that afterwards. I will post an answer as follow-up as it is difficult to put code in comments. – HankCa Jun 09 '17 at 05:09
  • 1
    Actually, there is no `AbstractControls.validators()` method. My last comment stands for if you meant `AbstractControls.validators()` and my answer will make this assumption also. – HankCa Jun 09 '17 at 05:44
  • 3
    This works but seems to only indicate validators that fail. For example when a control has a preset value and is required. It won't say so using this. I used a workaround where I store the value, set it to null, do this check, and restore the value. – Robin Jul 08 '20 at 14:40
  • Angular v12.2 introduced `hasValidator()` and a slew of other functions on the `AbstractControl` API for working with validators: https://github.com/angular/angular/pull/42838. See pasek's answer. – Adam Dunkerley Sep 14 '21 at 13:15
18

Angular v12.2 introduced hasValidator(): https://github.com/angular/angular/pull/42838

Example: this.formGroup.controls['anyCtrl'].hasValidator(Validators.required)

You can also add or remove validators later programatically. See addValidators(), removeValidators(), and others.

https://angular.io/api/forms/AbstractControl#hasValidator

Adam Dunkerley
  • 664
  • 6
  • 14
pasek
  • 347
  • 4
  • 11
  • 1
    This is now the correct answer. The AbstractControl API officially supports this. – Adam Dunkerley Sep 14 '21 at 13:11
  • 9
    "The provided validator must be a reference to the exact same function that was provided." This makes it pretty useless as well. If I set Validators.min(0), checking control.hasValidator(Validators.min(0)) returns false, only verifying the same exact instance of validation returns true. – Boat Nov 26 '21 at 12:06
  • 1
    how to use it for custom Validators? Can you provide an example ? – jason Apr 25 '23 at 17:49
6

This answer is a continuation of @joh04667's. They wrote:

public hasValidator(control: string, validator: string): boolean {
  return !!this.myForm.controls[control].validators(control).hasOwnProperty(validator);
 // returns true if control has the validator
}

However there is no AbstractControls.validators() method. I'm assuming AbstractControls.validator() was meant.

The hasValidator() method only works for validators that 'fail' (eg. a required validator on a control with the value '' (empty)). Since if they pass they return null. A way around this would be to set the value so that it always fails and restore that afterwards.

public hasValidator(control: string, validator: string): boolean {
    let control: AbstractControl = this.myForm.controls[control];
    let lastValue: any = control.value;
    switch(validator) {
        case 'required':
            control.setValue('');  // as is appropriate for the control
        case 'pattern':
            control.setValue('3'); // given you have knowledge of what the pattern is - say its '\d\d\d'
        ....
    }
    let hasValidator: boolean = !!control.validator(control).hasOwnProperty(validator);

    control.setValue(lastValue);
    return hasValidator;
}

And this is pretty horrible. It begs the question - Why is there no AbstractControl.getValidators(): ValidatorFn[]|null?

What is the motivation in hiding this? Perhaps they are worried someone might put in their code:

...
secretPassword: ['', [Validators.pattern('fjdfjafj734738&UERUEIOJDFDJj')]
...
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
HankCa
  • 9,129
  • 8
  • 62
  • 83
  • 1
    *"Why is there no AbstractControl.getValidators(): ValidatorFn[]|null?"* I'm just following this [**issue**](https://github.com/angular/angular/issues/13461) (without any perspective). – dev_054 Jun 10 '17 at 00:09
  • Yeah I commented on that ticket a couple months ago. No word from anyone relevant yet. :( – HankCa Jun 11 '17 at 07:36
  • What means the `!!` in your `hasValidator()` method? Thanks. – moreirapontocom Apr 28 '21 at 03:17
  • 1
    @moreirapontocom , it is called a double negative. Personally i don't fancy it, but it is a way of saying "if not null-like, be true" First makes it true or false and then reverses that, so "not (not null)" is what you get. See https://stackoverflow.com/questions/10467475/double-negation-in-javascript-what-is-the-purpose#10467486 – Nebulosar May 12 '21 at 08:19
3

There is no straight forward or clean way of doing this. Here is the cleanest method that I came across that works. Tested with the latest version of Angular v10.2.0 (as of today)

Import these

import {AbstractControl, FormControl, Validators} from '@angular/forms';

Define your control

anyCtrl = new FormControl('', [Validators.required]);

Add this method

  public hasRequiredField = (abstractControl: AbstractControl): boolean => {
    if (abstractControl.validator) {
      const validator = abstractControl.validator({}as AbstractControl);
      if (validator && validator.required) {
        return true;
      }
    }
    return false;
  }

How to call this method from the HTML

<input placeholder="Placeholder" [formControl]="anyCtrl" [required]="hasRequiredField(anyCtrl)">

Calling it from the Typescript file (logic) within the constructor or ngOnInit

constructor() {
  console.log(this.hasRequiredField(this.anyCtrl)); // true, false if Validators array does not contain Validators.required
}
Dilshan Liyanage
  • 4,440
  • 2
  • 31
  • 33
3

If you want to check if a controller has a certain validators, it is very simple for angular 12.2+

for example,

let controller = new FormControl('', [Validators.required])
console.log(controller.hasValidator(Validators.required))//true

However, this will not work for Validators.minLength(), Validators.maxLength(), Validators.min(), Validators.max or any validator that needs a parameter.

To make it work, you need to create a reference for that validator and add that reference in the form controller validators and in the hasValidator function.

For example,

const minLengthValidator = Validators.minLength(60);
let controller = new FormControl('', [minLengthValidator])
console.log(controller.hasValidator(minLengthValidator)) //true

but if you do the following, you get false:

let controller = new FormControl('', [ Validators.minLength(60)])
console.log(controller.hasValidator( Validators.minLength(60))) //false
1

Based on mtinner's commend https://github.com/angular/angular/issues/13461#issuecomment-340368046 we built our own directive to mark mandatory fields accordingly.

@Directive({
  selector: '[mandatoryField]'
})
export class MandatoryFieldDirective implements OnInit {

  hasRequiredField(abstractControl: AbstractControl) {
    if (abstractControl.validator) {
      const validator = abstractControl.validator({} as AbstractControl);
      if (validator && validator.required) {
        return true;
      }
    }
    return false;
  }

  ngOnInit() {
    const required = this.hasRequiredField(this.ngControl.control);
    if (required) {
      this.renderer.setAttribute(this.elementRef.nativeElement, 'required', '');

      if (this.parentFormField && this.parentFormField._elementRef) { // required for Angular Material form-fields
        this.renderer.setAttribute(this.parentFormField._elementRef.nativeElement, 'required', '');
      }
    }
  }

  constructor(
    private ngControl: NgControl, @Optional() private parentFormField: MatFormField,
    public renderer: Renderer2, public elementRef: ElementRef
  ) { }

}

The directive sets a 'required' attribute. This attribute can be addressed via CSS. The directive works on normal HTML input tags as well as on Angular Material form fields. To work with Angular Material we had to add a little workaround as the 'required' attribute has to be set on the enclosing form field tag; not only on the actual input field. Therefore the parent component is pass-through to the directive constructor.

<mat-form-field class="date-picker-form">
  <input matInput class="label-value" [formControlName]="controlName" mandatoryField [matDatepicker]="picker">
  <mat-datepicker #picker class="calendar"></mat-datepicker>
</mat-form-field>
lunanigra
  • 31
  • 4
1

Using latest Angular version v12.2 and hasValidator you can create pipe like this:

@Pipe({
  name: 'hasRequiredValidator',
})
export class HasRequiredValidatorPipe implements PipeTransform {

  transform(value: AbstractControl | null, controlName?: string): boolean {
    const control = controlName ? value?.get(controlName) : value;
    return !!control?.hasValidator(Validators.required);
  }

}

And in html template

<input [required]="form | hasRequiredValidator: 'controlName'">

Or

<input [required]="form.get('controlName') | hasRequiredValidator">
Dharman
  • 30,962
  • 25
  • 85
  • 135
spiotr12
  • 79
  • 6
0

I adjusted the code from joh04667 and HankCa to this:

export const hasValidator = (form: FormGroup, controlPath: string, validator: string): boolean => {
  const control = form.get(controlPath);
  const validators = control.validator(control);
  return !!(validators && validators.hasOwnProperty(validator));
};

Which I store in a file called util.ts and import in the component that contains the form like this:

import * as util from '@app/shared/util';

And define util in your class:

public util = util;

Add the directive to your input component like this:

[required]="util.hasValidator(myForm, 'path.to.control', 'required')"
0

It is important to keep in mind that by using setValidator method will overwrite your existing validators so you will need to include all the validators you need/want for the control that you are resetting.

    control.setValidators([myCustomValidator(owner)]);

There is no such clean way of doing this but u can always get the validators present in any form group or form control by

   this.form.controls[key].validator

and then to add your custom control we can do like

control.setValidators([control.validator, myCustomValidator(owner)]);

This will allow us to reuse the existing validators along with our new custom validator

0

Starting with Angular v12.2 the AbstractControl should have a new method:

hasValidator(validator: ValidatorFn): boolean;
Rene Juuse
  • 575
  • 8
  • 16
0

How to see all attached/added validators in reactive forms?

If you have added a few validators, like pattern, required, and minLength, and you use this:

  console.log(this.form.controls[key].validator)

the result will show only required validator. enter image description here

but if you do console.log(this.form.controls[key]) you will be able to see all attached validators to FormControl by checking the _rawValidators property:

enter image description here

just go with mouse over the specific validator, and based on info you get, you will know which validator is that, for ex. on image we see regex.test, which is used for pattern validator.

Dacili
  • 258
  • 4
  • 8