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

问题描述 投票:0回答:6

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

ControlValueAccessor
接口。

但是,我想访问与我的组件关联的

FormControl
实例。我使用带有
FormBuilder
的反应式表单,并使用
formControlName
属性提供表单控制。

SO,如何从自定义表单组件内部访问

FormControl
实例?

angular angular-forms
6个回答
70
投票

这个解决方案诞生于 Angular 存储库中的讨论。请务必阅读它,如果您对此问题感兴趣,最好参与。


我研究了

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
注入到组件中,然后使用通过
FormControl
绑定获得的控件名称从中获取特定的
formControlName

但是,请注意,此解决方案是专门针对在主机元素上使用

FormControlName
指令的用例量身定制的。在其他情况下它不会起作用。为此,您需要添加一些额外的逻辑。如果您认为这个问题应该由 Angular 解决,请务必访问讨论


62
投票

通过

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

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

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


21
投票

基于之前的答案和评论中发现的文档,我认为这是基于

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

请注意,使用此解决方案,您无需在组件上指定 NG_VALUE_ACCESSOR 提供程序,因为这将导致 CI 循环依赖。构造函数将正确设置 valueAccessor。


4
投票

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

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

然后您可以在自定义表单组件中获取表单控件实例,如下所示:

@Input() control: FormControl;

4
投票

这是已接受答案的简化/清理版本,适用于 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) { }
}

0
投票

您可以创建自定义指令(这是模板驱动表单的示例,但您可以注入

FormControlName
而不是
NgModel
并根据需要将选择器更改为
formControlName
):

@Directive({
    selector: '[ngModel]'
})
export class SetNgModelDirective {
    constructor(@Inject(NgModel) ngModel: NgModel, @Optional() @Inject(NG_VALUE_ACCESSOR) accessors: ControlValueAccessor[]) {
        if (accessors) {
            for (const accessor of accessors) {
                if (typeof accessor.setNgModel === 'function') {
                    accessor.setNgModel(ngModel);
                }
            }
        }
    }
}

任何其他方法迟早都会导致 DI 循环依赖性错误。

© www.soinside.com 2019 - 2024. All rights reserved.