How to get name of input field from Angular2 FormControl object?

42,307

Solution 1

To expand on Radim Köhler answer. here is a shorter way of writing that function.

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

Solution 2

We can use .parent property, well today ["_parent"] (see more below):

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

and now we are ready to adjust our validator definition

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

.parent later, ["_parent"] now

At the moment (today, now), current release is :

2.1.2 (2016-10-27)

But following this issue: feat(forms): make 'parent' a public property of 'AbstractControl'

And as already stated here

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

Features

  • forms: make 'parent' a public property of 'AbstractControl' (#11855) (445e592)
  • ...

we could later change the ["_parent"] into .parent

Solution 3

As of Angular 4.2.x, you can access a FormControl's parent FormGroup (and its controls) using the public parent property:

private formControl: FormControl;

//...

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

Solution 4

You have two options:

With the help of the Attribute decorator:

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

With the help of the Input decorator:

@Input() formControlName;

To use this your validation needs to be a directive of course.

Solution 5

You can set control name in validators:

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

And then:

validate(c: FormControl, name) {
    return name === 'controlName' ? {invalid: true} : null;
}
Share:
42,307
Michael Oryl
Author by

Michael Oryl

I'm the Director of Web Development for a life insurance company. In my previous life I was the founder and editor-in-chief of MobileBurn.com. For work I focus mostly on JavaScript projects using Node.js, Angular, and Node. I also dabble in iOS/Swift when needed, though I claim no proficiency there. I used to spend a lot of time with Java/Groovy, but I don't work in that space any longer. For fun, I like working on Arduino and similar micro-controllers for robotics projects as well as generic "maker" stuff. #SOreadytohelp

Updated on February 16, 2022

Comments

  • Michael Oryl
    Michael Oryl over 2 years

    I have an Angular 2 application that uses the ReactiveForms module to manage a form that uses a custom validator. The validator receives a FormControl object. I have a few input fields that could use the same custom validator if only I knew the name of the field when the FormControl was passed to the validator.

    I can't find any method or public property on FormControl that exposes the input field's name. It's simple enough to see its value, of course. The following shows how I would like to use it:

    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);
              });
        });
      }
    
  • Michael Oryl
    Michael Oryl over 7 years
    This certainly lets one gain access to the form, but how do you then identify which of the (many) fields in the form is represented by your original FormControl that was passed into the validator?
  • Radim Köhler
    Radim Köhler over 7 years
    I am not fully sure about your concern. The point is: The name of a FormControl is given only by its parent FormGroup. Because only such parent has a MAP of controls and the keys, represent the name. And because every control has the information, who is its .parent, we can get that information anywhere.. even inside of validator .. just by calling the above (or adjusted/optimized) function getControlName(controlPassedToValidtor). Other words, even single control, passed to validator function is enough - to get its name. Only condition is: it must be child of FormGroup
  • Michael Oryl
    Michael Oryl over 7 years
    The concern is access to the FormGroup gives me access to every FormControl it contains along with the name of every field they represent. But out of that list of FormControls I now have access to, how do I know which one is the FormControl that was passed into the validator. I need the name of a specific field. Unless there's something I'm missing, what you are providing is a list of all fields....
  • Radim Köhler
    Radim Köhler over 7 years
    Hmmm how to explain it. We have a control inside of the validator. We have a reference to it. We also have a dictionary a set of { Name1: control1, Name2: control2, ...}. Each control (1, 2, ...) is an instance of some FormControl (or other FormGroup or FormArray). So, we can compare the control passed to the validator with each of them. If the reference is the same control === parent.controls[name] we got a match. Such parent child is the same as the control passed to validator. Helping? or ... ? I mean, we have to have a control, which we do.. it was passed...
  • Radim Köhler
    Radim Köhler over 7 years
    I extended my answer, with more details in comments, and mostly - showed how inside of the validator we can call that function, and receive a name of that control... hope now it is clear
  • Michael Oryl
    Michael Oryl over 7 years
    OK, I will give that a shot. Thanks.
  • gerleim
    gerleim over 6 years
    Still (as of Aug of 2017) no better way than to iterate through all the controls?
  • FireDragon
    FireDragon over 6 years
    most succinct answer, but it's still clear what's going on. works for me in Angular 4. thanks
  • ShadSterling
    ShadSterling almost 6 years
    Wouldn't it be better to construct a Map of AbstractControls to names and look up in that, rather than generating the key list and then searching the key list for each lookup?
  • Mr AH
    Mr AH over 5 years
    you just saved me from a lot of pain... I had a async validator in reactiveforms which used the control name to go off to a service and get some validation information back. but it was always called initially on the new FormControl bit, so the parent was undefined, and if you return of(null) to get past the undefined parent, it will always set the control to VAILD even though there are errors on the next call!
  • lonix
    lonix almost 5 years
    Doesn't work: "Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ [key: string]: AbstractControl; } | AbstractControl[]'."
  • Nowdeen
    Nowdeen almost 4 years
    Here, in may 2020, I had to change this.formControl.parent.controls by this.formControl.value in the above suggested code.
  • kvetis
    kvetis almost 4 years
    This is very resource expensive operation. Imagine each control in form iterating over all components in form. Complexity of rendering form is O(n²). But doing this only occasionaly on single field is okay.
  • 321X
    321X almost 4 years
    @Ionix: Use this: Object.keys(c.parent.controls).find(name => c === c.parent.get(name))
  • Shailesh Bhat
    Shailesh Bhat about 3 years
    const formGroup = c["_parent"].controls; return Object.keys(formGroup).find(name => c === formGroup[name]) || null; this code worked for me @4.1.5 version of typescript. Works for all versions may be..But, I'm not sure
  • Michael Ziluck
    Michael Ziluck over 2 years
    This answer is wrong. If the name of the form control is an empty string, it will return null rather than the empty string due to the way the || operator works in JavaScript. You must use ?? instead.