表单 – 在Angular中包装FormControl(2)

我正在尝试在Angular(v5)中创建自定义表单控件.自定义控件本质上是Angular Material组件的包装器,但还有一些额外的东西.

我已经阅读了有关实现ControlValueAccessor的各种教程,但是我找不到任何可以编写组件来包装现有组件的内容.

理想情况下,我想要一个显示Angular Material组件的自定义组件(带有一些额外的绑定和东西),但是能够从父表单传递验证(例如,必需)并让Angular Material组件处理它.

例:

外部组件,包含表单并使用自定义组件

<form [formGroup]="myForm">
    <div formArrayName="things">
        <div *ngFor="let thing of things; let i = index;">
            <app-my-custom-control [formControlName]="i"></app-my-custom-control>
        </div>
    </div>
</form>

自定义组件模板

基本上我的自定义表单组件只包含一个Angular Material下拉列表和自动完成.我可以在不创建自定义组件的情况下做到这一点,但这样做似乎是有意义的,因为处理过滤等的所有代码都可以存在于该组件类中,而不是在容器类中(不需要)关心这个的实施).

<mat-form-field>
  <input matInput placeholder="Thing" aria-label="Thing" [matAutocomplete]="thingInput">
  <mat-autocomplete #thingInput="matAutocomplete">
    <mat-option *ngFor="let option of filteredOptions | async" [value]="option">
      {{ option }}
    </mat-option>
  </mat-autocomplete>
</mat-form-field>

因此,在输入更改时,该值应该用作表单值.

我尝试过的事情

我尝试了几种方法,都有自己的陷阱:

简单的事件绑定

绑定到输入上的键盘和模糊事件,然后通知父更改(即调用Angular传递给registerOnChange的函数作为实现ControlValueAccessor的一部分).

这种方法有效,但是从下拉列表中选择一个值时,似乎更改事件不会触发,并且最终会处于不一致状态.

它也不考虑验证(例如,如果它是“必需的”,当值为n时; t设置表单控件将正确无效,但Angular Material组件将不会如此显示).

嵌套表格

这有点接近了.我在自定义组件类中创建了一个新表单,它只有一个控件.在组件模板中,我将该表单控件传递给Angular Material组件.在类中,我订阅了valueChanges,然后将更改传播回父级(通过传递给registerOnChange的函数).

这种作品,但感觉凌乱,应该有一个更好的方法.

这也意味着应用于我的自定义表单控件(由容器组件)的任何验证都会被忽略,因为我创建了一个缺少原始验证的新“内部表单”.

根本不要使用ControlValueAccessor,而只是传入表单

正如标题所说……我试图不以“正确”的方式做到这一点,而是添加了一个绑定到父表单.然后,我在自定义组件中创建一个表单控件作为该父表单的一部分.

这适用于处理值更新和范围验证(但必须创建为组件的一部分,而不是父表单),但这只是错误.

摘要

处理这个问题的正确方法是什么?感觉我只是在绊倒不同的反模式,但我在文档中找不到任何暗示甚至支持它的东西.

编辑:

我已经添加了一个帮助程序来执行这个我已经启动的角度实用程序库:s-ng-utils.使用它可以扩展WrappedFormControlSuperclass并写入:

@Component({
  selector: 'my-wrapper',
  template: '<input [formControl]="formControl">',
  providers: [provideValueAccessor(MyWrapper)],
})
export class MyWrapper extends WrappedFormControlSuperclass<string> {
  // ...
}

查看更多文档here.

一种解决方案是获取对应于内部表单组件ControlValueAccessor的@ViewChild(),并在您自己的组件中委托给它.例如:

@Component({
  selector: 'my-wrapper',
  template: '<input ngDefaultControl>',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => NumberInputComponent),
      multi: true,
    },
  ],
})
export class MyWrapper implements ControlValueAccessor {
  @ViewChild(DefaultValueAccessor) private valueAccessor: DefaultValueAccessor;

  writeValue(obj: any) {
    this.valueAccessor.writeValue(obj);
  }

  registerOnChange(fn: any) {
    this.valueAccessor.registerOnChange(fn);
  }

  registerOnTouched(fn: any) {
    this.valueAccessor.registerOnTouched(fn);
  }

  setDisabledState(isDisabled: boolean) {
    this.valueAccessor.setDisabledState(isDisabled);
  }
}

上面模板中的ngDefaultControl是手动触发angular以将其正常的DefaultValueAccessor附加到输入.如果您使用< input ngModel>,则会自动执行此操作,但我们不希望此处使用ngModel,只需要值访问器.您需要将上面的DefaultValueAccessor更改为材料下拉列表的值访问器 – 我自己并不熟悉Material.

相关文章
相关标签/搜索