【问题标题】:How to get name of input field from Angular2 FormControl object?如何从 Angular2 FormControl 对象中获取输入字段的名称?
【发布时间】:2017-03-14 16:39:43
【问题描述】:

我有一个 Angular 2 应用程序,它使用 ReactiveForms 模块来管理使用自定义验证器的表单。验证器收到一个FormControl 对象。当FormControl 被传递给验证器时,我有几个输入字段可以使用相同的自定义验证器。

我在FormControl 上找不到任何公开输入字段名称的方法或公共属性。当然,这很简单,可以看出它的价值。以下显示了我想如何使用它:

public asyncValidator(control: FormControl): {[key: string]: any} {
  var theFieldName = control.someMethodOfGettingTheName(); // this is the missing piece

  return new Promise(resolve => {
      this.myService.getValidation(theFieldName, control.value)
        .subscribe(
          data => {
            console.log('Validation success:', data);
            resolve(null);
          },
          err => {
            console.log('Validation failure:', err);
            resolve(err._body);
          });
    });
  }

【问题讨论】:

    标签: angular typescript angular2-forms angular-forms


    【解决方案1】:

    你有两个选择:

    借助 Attribute 装饰器:

    constructor(@Attribute('formControlName') public formControlName) {}
    

    借助 Input 装饰器:

    @Input() formControlName;
    

    要使用这个,你的验证当然需要是一个指令。

    【讨论】:

      【解决方案2】:

      我们可以使用 .parent 属性,今天好了["_parent"] (见下文):

      export const getControlName = (control: ng.forms.AbstractControl) =>
      {
          var controlName = null;
          var parent = control["_parent"];
      
          // only such parent, which is FormGroup, has a dictionary 
          // with control-names as a key and a form-control as a value
          if (parent instanceof ng.forms.FormGroup)
          {
              // now we will iterate those keys (i.e. names of controls)
              Object.keys(parent.controls).forEach((name) =>
              {
                  // and compare the passed control and 
                  // a child control of a parent - with provided name (we iterate them all)
                  if (control === parent.controls[name])
                  {
                      // both are same: control passed to Validator
                      //  and this child - are the same references
                      controlName = name;
                  }
              });
          }
          // we either found a name or simply return null
          return controlName;
      }
      

      现在我们准备调整我们的验证器定义

      public asyncValidator(control: FormControl): {[key: string]: any} {
        //var theFieldName = control.someMethodOfGettingTheName(); // this is the missing piece
        var theFieldName = getControlName(control); 
        ...
      

      .parent 稍后,["_parent"] 现在

      目前(今天,现在),当前版本是:

      2.1.2 (2016-10-27)

      但是关注这个问题:feat(forms): make 'parent' a public property of 'AbstractControl'

      这里已经说过了

      2.2.0-beta.0 (2016-10-20)

      特点

      • forms:将“parent”设为“AbstractControl”的公共属性 (#11855) (445e592)
      • ...

      我们可以稍后将["_parent"] 更改为.parent

      【讨论】:

      • 这当然可以让一个人获得对表单的访问权限,但是您如何识别表单中的(许多)字段中的哪一个由传递给验证器的原始FormControl 表示?
      • 我不完全确定您的担忧。关键是:FormControl 的 name 仅由其父 FormGroup 给出。因为只有这样的父级具有controls 的 MAP 和键,代表 名称。而且因为每个控件都有信息,谁是它的.parent,我们可以在任何地方获取该信息.. 甚至在 validator 内部.. 只需调用上面的 (或调整/优化) 函数getControlName(controlPassedToValidtor)。换句话说,即使是单个控件,传递给验证器函数就足够了——得到它的名字。唯一的条件是:它必须是FormGroup的孩子
      • 关注的是对 FormGroup 的访问使我可以访问它包含的每个 FormControl 以及它们所代表的每个字段的名称。但是在我现在可以访问的 FormControl 列表中,我怎么知道 哪个 是传递给验证器的 FormControl。我需要特定字段的名称。除非我遗漏了什么,否则您提供的是所有字段的列表....
      • 嗯,怎么解释。我们在验证器内部有一个控件。我们有一个参考。我们还有一本字典,一组{ Name1: control1, Name2: control2, ...}。每个控件 (1, 2, ...) 都是某个 FormControl(或其他 FormGroup 或 FormArray)的一个实例。因此,我们可以将传递给验证器的 control 与它们中的每一个进行比较。如果引用是相同的control === parent.controls[name],我们得到了匹配。 这样父子与传递给验证器的控件相同。帮助?要么 ... ?我的意思是,我们必须有一个控制,我们做到了......它已经通过......
      • 我扩展了我的答案,在 cmets 中提供了更多详细信息,并且主要展示了我们如何在验证器内部调用该函数,并接收该控件的名称...希望现在很清楚
      【解决方案3】:

      不完全是您想要的,但您可以像在某些示例中那样动态创建验证器。

      喜欢

      typeBasedValidator(controlName: string): ValidatorFn {
        return(control: AbstractControl): {[key: string]: any} => {
           // Your code using controlName to validate
           if(controlName == "something") { 
             doSomething(); 
           } else { 
             doSomethingElse(); 
           }
        }
      }
      

      然后在创建表单时使用验证器,传递控件名称 喜欢

      【讨论】:

        【解决方案4】:

        从 Angular 4.2.x 开始,您可以使用公共 parent 属性访问 FormControl 的父 FormGroup(及其控件):

        private formControl: FormControl;
        
        //...
        
        Object.keys(this.formControl.parent.controls).forEach((key: string) => {
          // ...
        });
        

        【讨论】:

        • 在这里,在 2020 年 5 月,我不得不在上面建议的代码中将 this.formControl.parent.controls 更改为 this.formControl.value。
        【解决方案5】:

        扩展 Radim Köhler 的答案。这是编写该函数的更短的方法。

        getControlName(c: AbstractControl): string | null {
            const formGroup = c.parent.controls;
            return Object.keys(formGroup).find(name => c === formGroup[name]) || null;
        }
        

        【讨论】:

        • 最简洁的答案,但仍然清楚发生了什么。在 Angular 4 中为我工作。谢谢
        • 构造一个 AbstractControls 映射到名称并在其中查找,而不是生成键列表然后为每次查找搜索键列表不是更好吗?
        • 不起作用:"Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ [key: string]: AbstractControl; } | AbstractControl[]'."
        • 这是非常耗费资源的操作。想象一下表单中的每个控件都迭代表单中的所有组件。渲染形式的复杂度为 O(n²)。但是偶尔在单个字段上这样做是可以的。
        • @Ionix:使用这个:Object.keys(c.parent.controls).find(name => c === c.parent.get(name))
        【解决方案6】:

        您可以在验证器中设置控件名称:

        this.form = this.fb.group({
             controlName: ['', 
                 [
                    Validators.required, 
                    (c) => this.validate(c, 'controlName')
                 ]
              ]
        });
        

        然后:

        validate(c: FormControl, name) {
            return name === 'controlName' ? {invalid: true} : null;
        }
        

        【讨论】:

        • 你刚刚让我免于痛苦......我有一个反应形式的异步验证器,它使用控件名称转到服务并获取一些验证信息。但它最初总是在新的 FormControl 位上调用,因此父级未定义,如果您返回 of(null) 以越过未定义的父级,即使下次调用时出现错误,它也会始终将控件设置为 VAILD !
        【解决方案7】:

        接受答案的单行变体(也解决了我在评论中提到的错误)。

        getName(control: FormControl): string | null {
          return Object.entries(control.parent?.controls ?? []).find(([_, value]) => value === control)?.[0] ?? null;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-05-16
          • 2017-10-09
          • 2016-08-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-09
          • 2017-09-18
          相关资源
          最近更新 更多