从 Angular 中的自定义表单组件访问 FormControl

Get access to FormControl from the custom form component in Angular

我的 Angular 应用程序中有一个自定义表单控件组件,它实现了 ControlValueAccessor 接口。

但是,我想访问与我的组件关联的 FormControl 实例。我正在使用 FormBuilder 的反应式表单,并使用 formControlName 属性提供表单控制。

那么,如何从自定义表单组件内部访问 FormControl 实例?

此解决方案源自 Angular 存储库中的 the discussion。如果您对此问题感兴趣,请务必阅读或更好地参与。


我研究了 FormControlName 指令的代码,它启发了我编写以下解决方案:

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

  }

}

我将父 FormGroup 注入组件,然后使用通过 formControlName 绑定获得的控件名称从中获取特定的 FormControl

但是,请注意,此解决方案是专门针对在主机元素上使用 FormControlName 指令的用例量身定制的。它在其他情况下不起作用。为此,您需要添加一些额外的逻辑。如果您认为这应该由 Angular 解决,请务必访问 the discussion.

正如@Ritesh 已经在评论中写的那样,您可以将表单控件作为输入绑定传递:

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

然后您可以像这样在自定义表单组件中获取表单控件实例:

@Input() control: FormControl;

当通过 [formControl] 指令进行绑定时,使用 formControlName 作为输入参数不起作用。

这是一个无需任何输入参数即可双向工作的解决方案。

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

根据之前的答案和 documentation 在评论中找到的内容,这是我认为基于 ControlValueAccessor 的组件的最干净的解决方案。

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

这是已接受答案的 simplified/cleaned 更新版本,适用于 FormControlName 和 FormControl 输入:

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