45

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

7 Answers 7

48

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;
}
Sign up to request clarification or add additional context in comments.

7 Comments

most succinct answer, but it's still clear what's going on. works for me in Angular 4. thanks
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?
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[]'."
@Ionix: Use this: Object.keys(c.parent.controls).find(name => c === c.parent.get(name))
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
|
29

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

7 Comments

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?
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
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....
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...
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
|
11

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

1 Comment

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

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) => {
  // ...
});

1 Comment

Here, in may 2020, I had to change this.formControl.parent.controls by this.formControl.value in the above suggested code.
6

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.

Comments

1

A one-line variation of the accepted answer (also resolves the bug I mention in the comment).

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

1 Comment

Brilliant! Saved a day.
0

Not exactly what you want but you could dynamically create the validator like in some examples.

like

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

Then use the validator when creating the form, passing the control name like

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.