Get validators present in FormGroup/FormControl

前端 未结 5 1769
感动是毒
感动是毒 2020-12-10 11:03

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

相关标签:
5条回答
  • 2020-12-10 11:15

    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].validators(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 effects 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!

    0 讨论(0)
  • 2020-12-10 11:17

    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')]
    ...
    
    0 讨论(0)
  • 2020-12-10 11:19

    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 讨论(0)
  • 2020-12-10 11:20

    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>
    
    0 讨论(0)
  • 2020-12-10 11:28

    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
    }
    
    0 讨论(0)
提交回复
热议问题