在我的自定义窗体控件上收到"validators.map 不是函数"错误



我创建了一个组件系列,用作ControlValueAccessor的表单控件。 将formControlNameformGroupNameformGroupArray应用于我的组件以传入表单控件时,我收到错误告诉我

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,这在习惯上是不正确的。

FormControlDirectiveFormControlNameFormGroupNameFormArrayNameFormGroupDirective是基于形式控制的指令,当它们组合在一起时,形成一个AbstractControlDirective树(因为每个指令都继承自AbstractControlDirective(。这些指令是视图层(由ControlValueAccessor处理(和模型层(由AbstractControl处理 -FormControlFormGroupFormArray的基类(之间的桥梁。

这种树的根是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实例(它仅借助formControlngModel指令处理独立的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以及事物如何协同工作的更多信息。