我创建了一个组件系列,用作ControlValueAccessor
的表单控件。 将formControlName
、formGroupName
或formGroupArray
应用于我的组件以传入表单控件时,我收到错误告诉我
Validators.map 不是一个函数
这就是我的组件的设置方式
@Component({
selector: 'view-box-form-component',
templateUrl: './view-box-form.component.html',
styleUrls: ['./view-box-form.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(()=>ViewBoxFormComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(()=>ViewBoxFormComponent)
}
]
})
export class ViewBoxFormComponent implements OnInit, ControlValueAccessor {
ViewBoxFormData: FormGroup;
constructor() { }
ngOnInit() {}
public onTouched : ()=> void =()=>{};
writeValue(val: any):void{ val && this.ViewBoxFormData.setValue(val, {emitEvent: false}); console.log(val); }
registerOnChange(fn: any): void{ this.ViewBoxFormData.valueChanges.subscribe(fn); console.log(fn); }
registerOnTouched(fn: any): void{ this.onTouched = fn; }
setDisabledState?(isDisabled: boolean): void{ isDisabled ? this.ViewBoxFormData.disable() : this.ViewBoxFormData.enable(); }
validate(c: AbstractControl): ValidationErrors | null{
return this.ViewBoxFormData.valid ? null : {invalidForm:{valid: false, message: 'field invalid'}};
}
}
这是该组件的模板
<section [formGroup]="ViewBoxFormData" class="text-control-section">
<label class="control-label">
<p class="smallText"> x: </p>
<input type="text" class="text-control smallText" formControlName="x" />
</label>
<label class="control-label">
<p class="smallText"> y: </p>
<input type="y" class="text-control smallText" formControlName="y" />
</label>
<label class="control-label">
<p class="smallText"> width: </p>
<input type="text" class="text-control smallText" formControlName="width" />
</label>
<label class="control-label">
<p class="smallText"> height: </p>
<input type="text" class="text-control smallText" formControlName="height" />
</label>
</section>
在模板到父组件内部,我像这样实现组件
<form [formGroup]="SvgFormData" (ngSubmit)="onSubmit()">
<view-box-form-component formGroupName="viewBox"></view-box-form-component>
</form>
我还需要传递其他几个组件FormArray
我正在尝试使用formArrayName="someControl"
. 我没有在组件中创建表单,而是使用基于 JSON 对象的函数生成表单。 用于为视图框数据创建窗体组的函数如下所示。
export function CreateViewBoxForm(data?: ViewBoxParameters): FormGroup{
return new FormGroup({
x : new FormControl((data ? data.x : ''), Validators.required),
y : new FormControl((data ? data.y : ''), Validators.required),
width : new FormControl((data ? data.width : ''), Validators.required),
height : new FormControl((data ? data.height : ''), Validators.required)
}) as FormGroup;
}
当我console.log()
完成的表单数据时,所有内容都使用所有正确的值正确定义,我只是无法弄清楚如何让它们正确传递到我的组件中。 有没有人看到我做错了什么,或者我是否需要做更多的事情才能使其正常工作?
更新
到目前为止,我决定添加整个机制的堆栈闪电战,以便你们可以更好地了解我需要用我的组件结构完成什么。
堆栈闪电战演示
我认为您必须将multi: true
添加到NG_VALIDATORS
令牌中。
此外,看起来您的ViewBoxFormComponent
不是一个简单的FormControl
,因为在您的组件中您还有另一个FormGroup
实例。IMO,这在习惯上是不正确的。
FormControlDirective
、FormControlName
、FormGroupName
、FormArrayName
、FormGroupDirective
是基于形式控制的指令,当它们组合在一起时,形成一个AbstractControlDirective
树(因为每个指令都继承自AbstractControlDirective
(。这些指令是视图层(由ControlValueAccessor
处理(和模型层(由AbstractControl
处理 -FormControl
、FormGroup
、FormArray
的基类(之间的桥梁。
这种树的根是FormGroupDirective
(通常绑定到form
元素:<form [formGroup]="formGroupInstance">
(。考虑到这一点,您的AbstractControl
树将如下所示:
FG <- [formGroup]="SvgFormData"
|
FC <- formGroupName="viewBox"
/ |
/ |
FC FC FC (height, width, x etc...)
FormControl
应该没有后代。在这种情况下,因为您的自定义组件(ViewBoxFormComponent
( 创建了另一个树。这并没有错,但是使用这种方法,您必须正确处理这两棵树,以免获得意外结果。
绑定到<section [formGroup]="ViewBoxFormData" class="text-control-section">
的FormGroup
实例不会注册为SvgFormData
的后代,因为FormGroupDirective
的行为不像FormGroupName
:
表单组指令
constructor(
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
super();
}
表单组名称
constructor(
@Optional() @Host() @SkipSelf() parent: ControlContainer, // `ControlContainer` - token for `FormGroupDirective`/`FormGroupName`/`FormArrayName`
@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
super();
this._parent = parent;
this._validators = validators;
this._asyncValidators = asyncValidators;
}
如果您的自定义组件不包含简单的FormControl
实例(它仅借助formControl
或ngModel
指令处理独立的FormControl
实例(,则应将其注册为后代表单组。
这可以通过这种方法实现:
parent.component.ts
// The shape of the form is defined in one place!
// The children must only comply with the shape defined here
this.SvgFormData = this.fb.group({
ViewBoxFormData: this.fb.group({
height: this.fb.control(''),
width: this.fb.control(''),
/* ... */
})
})
父组件.html
<form [formGroup]="SvgFormData" (ngSubmit)="onSubmit()">
<!-- this will be a child form group -->
<view-box-form-component></view-box-form-component>
</form>
view-box-form-component.component.ts
@Component({
selector: 'view-box-form-component',
templateUrl: './view-box-form.component.html',
styleUrls: ['./view-box-form.component.css'],
providers: [
{
provide: NG_VALIDATORS,
useExisting: forwardRef(()=>ViewBoxFormComponent),
multi: true,
}
],
viewProviders: [
{ provide: ControlContainer, useExisting: FormGroupDirective }
]
})
export class ViewBoxFormComponent implements OnInit {
constructor() { }
ngOnInit() {}
validate(c: AbstractControl): ValidationErrors | null{
/* ... */
}
}
如您所见,不再需要提供CONTROL_VALUE_ACCESSOR
,因为表单的形状将仅在一个位置(即:父表单容器(定义。在子窗体容器中,您所要做的就是提供与父窗体中定义的形状相关的正确路径:
view-box-form-component.component.html
<section formGroupName="ViewBoxFormData" class="text-control-section">
<label class="control-label">
<p class="smallText"> x: </p>
<input type="text" class="text-control smallText" formControlName="x" />
</label>
<label class="control-label">
<p class="smallText"> y: </p>
<input type="y" class="text-control smallText" formControlName="y" />
</label>
<label class="control-label">
<p class="smallText"> width: </p>
<input type="text" class="text-control smallText" formControlName="width" />
</label>
<label class="control-label">
<p class="smallText"> height: </p>
<input type="text" class="text-control smallText" formControlName="height" />
</label>
</section>
viewProviders
所做的是让您使用为 host 元素声明的令牌(在本例中为ControlContainer
(:
// FormGroupName
constructor (@Optional() @Host() @SkipSelf() parent: ControlContainer,) {}
注意:相同的方法可以应用于FormArrayName
。
您可以在 Angular Forms 的彻底探索中找到有关树AbstractControl
以及事物如何协同工作的更多信息。