Get access to FormControl from the custom form component in Angular

AngularAngular Forms

Angular Problem Overview


I have a custom form control component in my Angular application, which implements ControlValueAccessor interface.

However, I want to access the FormControl instance, associated with my component. I'm using reactive forms with FormBuilder and providing form control using formControlName attribute.

SO, how do I access FormControl instance from inside of my custom form component?

Angular Solutions


Solution 1 - Angular

This solution was born from the discussion in the Angular repository. Please, make sure to read it or even better to participate if you are interested in this problem.


I've studied the code of FormControlName directive and it's inspired me to write the following solution:

@Component({
  selector: 'my-custom-form-component',
  templateUrl: './custom-form-component.html',
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: CustomFormComponent,
    multi: true
  }]
})
export class CustomFormComponent implements ControlValueAccessor, OnInit {

  @Input() formControlName: string;

  private control: AbstractControl;


  constructor (
    @Optional() @Host() @SkipSelf()
    private controlContainer: ControlContainer
  ) {
  }


  ngOnInit () {

    if (this.controlContainer) {
      if (this.formControlName) {
        this.control = this.controlContainer.control.get(this.formControlName);
      } else {
        console.warn('Missing FormControlName directive from host element of the component');
      }
    } else {
      console.warn('Can\'t find parent FormGroup directive');
    }

  }

}

I'm injecting the parent FormGroup to the component and then getting the specific FormControl from it using control name obtained through formControlName binding.

However, be advised, that this solution is tailored specifically for the use case where FormControlName directive is used on host element. It won't work in other cases. For this you will need to add some additional logic. If you think, that this should be addressed by Angular, make sure to visit the discussion.

Solution 2 - Angular

Using formControlName as an input parameter doesn't work when binding via the [formControl] directive.

Here is a solution that works both ways without any input parameters.

export class MyComponent implements AfterViewInit {

  private control: FormControl;

  constructor(
    private injector: Injector,
  ) { }

  // The form control is only set after initialization
  ngAfterViewInit(): void {
    const ngControl: NgControl = this.injector.get(NgControl, null);
    if (ngControl) {
      this.control = ngControl.control as FormControl;
    } else {
      // Component is missing form control binding
    }
  }
}

Solution 3 - Angular

Building upon previous answers and documentation found in a comment, here is what my opinion is the cleanest solution for a ControlValueAccessor based component.

// No FormControl is passed as input to MyComponent
<my-component formControlName="myField"></my-component>
export class MyComponent implements AfterViewInit, ControlValueAccessor  {

  constructor(@Optional() @Self() public ngControl: NgControl) {
    if (ngControl != null) {
      // Setting the value accessor directly (instead of using
      // the providers) to avoid running into a circular import.
      ngControl.valueAccessor = this;
    }
  }

    ngAfterContentInit(): void {
       const control = this.ngControl && this.ngControl.control;
       if (control) {
          // FormControl should be available here
       }
    }
}

Solution 4 - Angular

As @Ritesh has already written in the comment you can pass form control as an input binding:

<my-custom-form-component [control]="myForm.get('myField')" formControlName="myField">
</my-custom-form-component>

And then you can get form control instance inside your custom form component like this:

@Input() control: FormControl;

Solution 5 - Angular

Here is a simplified/cleaned up version of the accepted answer that works for both FormControlName and FormControl inputs:

export class CustomFormComponent implements ControlValueAccessor, OnInit {

  @Input() formControl: FormControl;

  @Input() formControlName: string;

  // get ahold of FormControl instance no matter formControl or formControlName is given.
  // If formControlName is given, then controlContainer.control is the parent FormGroup/FormArray instance.
  get control() {
    return this.formControl || this.controlContainer.control.get(this.formControlName);
  }

  constructor(private controlContainer: ControlContainer) { }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSlava Fomin IIView Question on Stackoverflow
Solution 1 - AngularSlava Fomin IIView Answer on Stackoverflow
Solution 2 - AngularRandyView Answer on Stackoverflow
Solution 3 - AngularRui MarquesView Answer on Stackoverflow
Solution 4 - AngularYuri BeliakovView Answer on Stackoverflow
Solution 5 - AngularBrad CView Answer on Stackoverflow