我正在尝试在Angular(v5)中创建自定义表单控件 . 自定义控件本质上是Angular Material组件的包装器,但还有一些额外的东西 .
我已经阅读了有关实现 ControlValueAccessor
的各种教程,但是我找不到任何可以编写组件来包装现有组件的内容 .
理想情况下,我想要一个显示Angular Material组件的自定义组件(带有一些额外的绑定和内容),但是能够从父表单传递验证(例如 required
)并让Angular Material组件处理它 .
例:
Outer component, containing a form and using custom component
<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>
Custom component template
基本上我的自定义表单组件只包含一个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>
因此,在 input
更改时,该值应该用作表单值 .
我尝试过的事情
我尝试了几种方法,都有自己的陷阱:
简单事件绑定
绑定到 input
上的 keyup
和 blur
事件,然后通知父更改(即调用Angular传入 registerOnChange
的函数作为实现 ControlValueAccessor
的一部分) .
这种方式有效,但是从下拉列表中选择一个值时,似乎更改事件不会触发,并且最终会处于不一致状态 .
它也不考虑验证(例如,如果它是“必需的”,当值为n时; t设置表单控件将正确无效,但Angular Material组件将不会如此显示) .
嵌套表格
这有点接近了 . 我在自定义组件类中创建了一个新表单,它只有一个控件 . 在组件模板中,我将该表单控件传递给Angular Material组件 . 在类中,我订阅了 valueChanges
,然后将更改传播回父级(通过传递给 registerOnChange
的函数) .
这种作品,但感觉凌乱,应该有一个更好的方法 .
这也意味着应用于我的自定义表单控件(由容器组件)的任何验证都会被忽略,因为我创建了一个缺少原始验证的新“内部表单” .
根本不要使用ControlValueAccessor,而只是传入表单
正如 Headers 所说......我试图不以“正确”的方式做到这一点,而是添加了一个绑定到父表单 . 然后,我在自定义组件中创建一个表单控件作为该父表单的一部分 .
这适用于处理值更新和范围验证(但必须创建为组件的一部分,而不是父表单),但这只是错误 .
摘要
处理这个问题的正确方法是什么?感觉我只是在绊倒不同的反模式,但我在文档中找不到任何暗示甚至支持它的东西 .
3 回答
Edit:
我've added a helper for doing just this an angular utilities library I'已经开始了:s-ng-utils . 使用它你可以扩展
WrappedFormControlSuperclass
并写:查看更多文档here .
一种解决方案是获取对应于内部表单组件
ControlValueAccessor
的@ViewChild()
,并在您自己的组件中委托给它 . 例如:上面模板中的
ngDefaultControl
是手动触发角度以将其正常DefaultValueAccessor
附加到输入 . 如果您使用<input ngModel>
,则会自动发生这种情况,但我们不希望此处使用ngModel
,只需要值访问器 . 您需要将上面的DefaultValueAccessor
更改为材料下拉列表的值访问者 - 我自己并不熟悉Material .我实际上已经把这个问题包围了一段时间,我找到了一个非常相似(或相同)的好解决方案,因为Eric _840029使用了@ViewChild valueAccessor,直到视图实际加载为止(参见@ViewChild docs)
这是解决方案:(我给你的例子是用NgModel包装一个核心的角度选择指令,因为你使用自定义的formControl,你需要定位那个formControl的valueAccessor类)
NgForm提供了一种简单的方法来管理表单,而无需在HTML表单中注入任何数据 . 输入数据必须在组件级别注入,而不是在传统的html标记中注入 .
其他方法是创建一个表单组件,其中所有数据模型都使用ngModel绑定;)