我在我的Angular应用程序中有一个自定义表单控件组件,它实现了ControlValueAccessor
接口。
但是,我想要访问与我的组件相关联的FormControl
实例。我正在使用具有FormBuilder
的响应式表单,并使用formControlName
属性提供表单控件。
那么,我如何从我的自定义表单组件内部访问FormControl
实例?
我在我的Angular应用程序中有一个自定义表单控件组件,它实现了ControlValueAccessor
接口。
但是,我想要访问与我的组件相关联的FormControl
实例。我正在使用具有FormBuilder
的响应式表单,并使用formControlName
属性提供表单控件。
那么,我如何从我的自定义表单组件内部访问FormControl
实例?
这个解决方案来源于 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
注入到组件中,然后使用通过formControlName
绑定获取的控件名称从中获取特定的FormControl
。
但是,请注意,此解决方案专门针对在宿主元素上使用FormControlName
指令的用例进行了定制。在其他情况下,它将无法正常工作。为此,您需要添加一些额外的逻辑。如果您认为这应该由Angular解决,请确保访问讨论。
当通过[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
}
}
}
get is deprecated: from v4.0.0 use Type<T> or InjectionToken<T>
。 - AsGoodAsItGetsconst ngControl = this.injector.get<NgControl>(NgControl as Type<NgControl>);
- CharlesngAfterViewInit()
给我带来了不一致的状态错误,但将其更改为 ngOnInit()
就像魔法一样奏效!非常感谢,比被接受的答案好多了。 - Alan Sereb基于之前的回答和在评论中找到的文档,以下是我认为最干净的解决方案,用于基于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。provide: NG_VALUE_ACCESSOR
,以避免循环依赖。请参见:https://material.angular.io/guide/creating-a-custom-form-field-control#ngcontrol - crashbus正如@Ritesh在评论中已经写过的,您可以将表单控件作为输入绑定传递:
<my-custom-form-component [control]="myForm.get('myField')" formControlName="myField">
</my-custom-form-component>
然后你可以在自定义表单组件内通过以下方式获取表单控件实例:
@Input() control: 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) { }
}
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);
}
}
}
}
}
export class MyComponent implements ControlValueAccessor {
setNgModel(ngModel: NgModel): void {
const control = this.ngModel.control;
// ...
}
}
FormControl
而不是使用NG_VALUE_ACCESSOR
提供程序可能是最稳健的解决方案。 - Coderer