我的自定义表单控件上收到“validators.map 不是函数”错误

Opt*_*tiq 2 typescript angular angular-forms

我创建了一系列组件用作表单控件ControlValueAccessor。当应用formControlName,formGroupNameformGroupArray我的组件传递表单控件时,我收到错误消息

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


}
Run Code Online (Sandbox Code Playgroud)

这是该组件的模板

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

Run Code Online (Sandbox Code Playgroud)

在父组件的模板内,我像这样实现组件

<form [formGroup]="SvgFormData" (ngSubmit)="onSubmit()">

    <view-box-form-component formGroupName="viewBox"></view-box-form-component>

</form>
Run Code Online (Sandbox Code Playgroud)

我还有几个其他组件需要将FormArrays 传递给我尝试使用的组件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;
  }
Run Code Online (Sandbox Code Playgroud)

当我console.log()完成表单数据时,所有内容都已使用所有正确的值正确定义,我只是不知道如何让它们正确地传递到我的组件中。有人知道我做错了什么,或者我是否需要做更多的事情才能使其正常工作?

更新

到目前为止,我决定添加整个机制的 stackblitz,以便你们可以更好地了解我需要用我的组件结构来完成什么。

堆栈闪电战演示

And*_*tej 7

我认为您必须添加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...)

Run Code Online (Sandbox Code Playgroud)

应该FormControl没有后代。在这种情况下,因为您的自定义组件(ViewBoxFormComponent)创建了另一棵树。这并没有错,但是使用这种方法,您必须正确处理这两棵树,以免得到意外的结果。

FormGroup绑定到 的实例不会<section [formGroup]="ViewBoxFormData" class="text-control-section"> 注册为 的后代,SvgFormData因为 的FormGroupDirective行为不像FormGroupName

表单组指令

constructor(
      @Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
      @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
    super();
  }
Run Code Online (Sandbox Code Playgroud)

表单组名

  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;
  }
Run Code Online (Sandbox Code Playgroud)

如果您的自定义组件不打算包含简单FormControl实例(它仅在或指令的帮助下处理独立 实例),您应该将其注册为后代表单组。 这可以通过以下方法实现:FormControlformControlngModel

父组件.ts

   FG <- [formGroup]="SvgFormData"
    |
    FC <- formGroupName="viewBox"
  / | \
 /  |  \
FC  FC  FC (height, width, x etc...)

Run Code Online (Sandbox Code Playgroud)

父组件.html

constructor(
      @Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
      @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
    super();
  }
Run Code Online (Sandbox Code Playgroud)

视图框表单组件.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{
    /* ... */
  }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,无需再提供CONTROL_VALUE_ACCESSOR,因为表单的形状将仅在一个位置定义(即:父表单容器)。您在子表单容器中所需要做的就是提供与父表单中定义的形状相关的正确路径:

view-box-form-component.component.html

  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;
  }
Run Code Online (Sandbox Code Playgroud)

所做viewProviders的就是让您使用ControlContainer为主机元素声明的令牌(在本例中):

// FormGroupName

constructor (@Optional() @Host() @SkipSelf() parent: ControlContainer,) {}
Run Code Online (Sandbox Code Playgroud)

注意:同样的方法可以应用于FormArrayName.

您可以在彻底探索 Angular Forms中找到有关AbstractControl树以及事物如何协同工作的更多信息。