【问题标题】:Get validators present in FormGroup/FormControl获取 FormGroup/FormControl 中存在的验证器
【发布时间】:2017-10-05 21:25:59
【问题描述】:

我在我的应用程序中使用 Material 2,但在这个问题中,我想专门解决 Input 的问题。

正如您在 API 参考 中看到的,有一个名为 required 的属性绑定,它在占位符中显示为星号。

所以,我想知道是否有办法检查表单控件在 Angular 中是否有特定的验证器,因为我真的不想为每个输入手动设置[required]="true/false"

我阅读了AbstractControl 文档,但没有找到任何相关信息。我遇到了hasError 方法讽刺的是没有记录在 nowhere ......无论是在 FormGroup 还是在 FormControl 或AbstractControl),但这不是我想要的。它只是检查表单控件是否有错误,但正如您可能已经阅读的那样,我想检查该控件是否有一些特定的验证器...

一些代码:

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

我希望问题足够清楚。提前致谢。

【问题讨论】:

  • 是的,我检查了错误并意识到其他一些问题,所以你最好等待比我聪明的人:P
  • 无论如何,谢谢你的尝试:)
  • @AJT_82 我刚刚开始赏金...如果您有解决方案甚至解决方法,请发布。
  • 是的,我一定会在有时间的时候尝试一下。这个问题开始困扰我:P
  • @AJT_82 这是糟糕的解决方法:) plnkr.co/edit/qUtwnwLYh1YSH4zr8ZK4?p=preview

标签: angular typescript angular-material2 angular2-forms


【解决方案1】:

Angular 并没有真正提供一个很好的、干净的方法来做到这一点,但它是可能的。我认为验证器存储在注入到 FormBuilder(NG_VALIDATORS) 中的服务中,我将研究劫持该服务或将其注入到组件中,但现在这将起作用:

docs 和源在AbstractControl 上显示validator 成员键入ValidatorFnValidatorFn 不幸的是只有 null 打字,所以我们看不到发生了什么。但是,在查看生成的源并探测应用程序之后,似乎我们可以将这个validators 方法传递给control 参数,该参数将返回该控件上存在的所有验证器的对象,无论它是否通过。

奇怪的是,这个适用于 FormControl 本身而不是 FormGroup(在 FormGroup 上,validators 成员不是函数,并且始终是 null我的测试)。编译后的 JS 说这个函数有一个control 参数;我试过传入FormControl 引用,但据我所知,只要此参数不为空,它只会返回控件上的验证器。

在 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}

更容易抓取:

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

在您的标记中:

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

Validators.required 的特殊情况

required 验证器有一个快捷方式。 [required] 绑定实际上是 RequiredValidator directive 的一个实例(source/forms.js 的第 5022 行)。该指令实际上会将required Validator 添加到它所在的FormControl。相当于在初始化时将Validators.required 添加到FormGroup。因此,将绑定属性设置为 false 将从该控件中删除 required Validator,反之亦然...无论哪种方式,该指令都会影响 FormControl.required 值,因此将其绑定到它更改的属性不会真正起作用很多。

唯一的区别是[required] 指令将星号添加到占位符,而Validators.required 没有。

我将继续研究 NG_VALIDATORS,但我希望这对现在有所帮助!

【讨论】:

  • 您好,@joh04667,首先,我只想说:谢谢。我刚刚测试了您的解决方案here,它似乎有效。但是,值得一提的是,如果您有一个没有验证器的control,它会引发错误,换句话说,validatorsnull。测试它here。话虽如此,多谢一次。
  • 啊,关于你的最后一句话:我知道这就是 差异 只是 星号,这就是我想要它的原因 :) 我希望有一个一天,Material 2 团队实现了一个解决方案来添加星号来寻找响应式表单,而无需在 HTML 本身中设置 required。 :)
  • AFAIK hasValidator() 方法仅适用于“失败”的验证器(即,控件上的必需验证器,值为“”(emtpy))。因为如果他们通过了,他们会返回 null。解决此问题的一种方法是设置该值,使其始终失败并在之后恢复。我将发布一个答案作为后续,因为很难将代码放入 cmets。
  • 其实没有AbstractControls.validators()方法。如果您的意思是AbstractControls.validators(),我的最后一条评论代表我的回答也会做出这个假设。
  • 这可行,但似乎只表明验证器失败。例如,当控件具有预设值并且是必需的时。它不会这么说使用这个。我使用了一种解决方法来存储值,将其设置为 null,执行此检查并恢复值。
【解决方案2】:

这个答案是@joh04667's 的延续。他们写道:

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

但是没有AbstractControls.validators() 方法。我假设AbstractControls.validator() 是故意的。

hasValidator() 方法仅适用于“失败”的验证器(例如,控件上的必需验证器,值为 ''(空))。因为如果他们通过了,他们会返回 null。解决此问题的一种方法是设置该值,使其始终失败并在之后恢复。

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;
}

这太可怕了。它引出了一个问题 - 为什么没有AbstractControl.getValidators(): ValidatorFn[]|null

隐藏这个的动机是什么?也许他们担心有人可能会输入他们的代码:

...
secretPassword: ['', [Validators.pattern('fjdfjafj734738&UERUEIOJDFDJj')]
...

【讨论】:

  • “为什么没有 AbstractControl.getValidators(): ValidatorFn[]|null?” 我只是关注这个issue(没有任何观点)。跨度>
  • 是的,几个月前我评论过那张票。还没有任何相关人士的消息。 :(
  • !! 在您的hasValidator() 方法中是什么意思?谢谢。
  • @moreirapontocom ,它被称为双重否定。就我个人而言,我不喜欢它,但它是一种说法,“如果不是空的,那就是真的”首先使其为真或假,然后将其反转,所以“非(非空)”就是你得到的。见stackoverflow.com/questions/10467475/…
【解决方案3】:

没有直接或干净的方式来做到这一点。这是我遇到的工作最干净的方法。使用最新版本的 Angular v10.2.0 测试(截至今日)

导入这些

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

定义你的控制

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

添加此方法

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

如何从 HTML 调用这个方法

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

从构造函数或 ngOnInit 中的 Typescript 文件(逻辑)调用它

constructor() {
  console.log(this.hasRequiredField(this.anyCtrl)); // true, false if Validators array does not contain Validators.required
}

【讨论】:

    【解决方案4】:

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

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

    您也可以稍后以编程方式添加或删除验证器。请参阅addValidators()removeValidators() 等。

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

    【讨论】:

    • 这是正确的答案。 AbstractControl API 正式支持这一点。
    • "提供的验证器必须是对所提供的完全相同函数的引用。"这也使它变得毫无用处。如果我设置 Validators.min(0),检查 control.hasValidator(Validators.min(0)) 会返回 false,只有验证相同的确切验证实例才会返回 true。
    【解决方案5】:

    根据 mtinner 的推荐 https://github.com/angular/angular/issues/13461#issuecomment-340368046,我们构建了自己的指令来相应地标记必填字段。

    @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
      ) { }
    
    }
    

    该指令设置了一个“必需”属性。这个属性可以通过 CSS 来解决。该指令适用于普通的 HTML 输入标签以及 Angular Material 表单字段。要使用 Angular Material,我们必须添加一些解决方法,因为必须在封闭的表单字段标签上设置“必需”属性;不仅在实际输入字段上。因此父组件传递给指令构造函数。

    <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>
    

    【讨论】:

      【解决方案6】:

      我将 joh04667 和 HankCa 的代码调整为:

      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));
      };
      

      我将其存储在一个名为 util.ts 的文件中并导入包含如下表单的组件中:

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

      并在你的类中定义 util:

      public util = util;
      

      像这样将指令添加到您的输入组件中:

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

      【讨论】:

        【解决方案7】:

        请务必记住,使用 setValidator 方法将覆盖您现有的验证器,因此您需要为要重置的控件包含所有需要/想要的验证器。

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

        没有这样干净的方法可以做到这一点,但你总是可以让验证器出现在任何表单组或表单控件中

           this.form.controls[key].validator
        

        然后添加您的自定义控件,我们可以这样做

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

        这将使我们能够重用现有的验证器以及新的自定义验证器

        【讨论】:

          【解决方案8】:

          Angular v12.2 开头的AbstractControl 应该有a new method

          hasValidator(validator: ValidatorFn): boolean;
          

          【讨论】:

            【解决方案9】:

            使用最新的 Angular 版本 v12.2 和 hasValidator 你可以像这样创建管道:

            @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);
              }
            
            }
            

            在html模板中

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

            或者

            <input [required]="form.get('controlName') | hasRequiredValidator">
            

            【讨论】:

              猜你喜欢
              • 2018-07-10
              • 1970-01-01
              • 2020-07-30
              • 2021-04-19
              • 2018-11-11
              • 2021-11-17
              • 2017-09-20
              • 2021-01-07
              • 1970-01-01
              相关资源
              最近更新 更多