我不喜欢 Angular 的任何一个表单验证路径,但我正在研究一种方法来结合每个路径的优点,并且接近我喜欢的答案。
我知道无论我是否去 formBuilder
或 ngModel
路线,有一个NgForm
对于表单,它有一个持 Root过的属性 FormGroup
它具有 FormControl
的异构集合对象。 HTML 元素都有一个适配器对象实现 ControlValueAccessor
界面,以及我自己的 Angular 组件,如 <date-range-picker>
可以实现相同的接口(interface)并假装只是另一个元素,其值是任意复杂的对象。每个FormControl
包装一个元素,通过 ControlValueAccessor
与它对话接口(interface),所以它不知道它到底在和什么对话。
我知道放置 ngModel
或 formControl
元素上的指令将创建 FormControl
该元素的实例;该元素不会自动获得一个,即使是 <form>
标签得到 NgForm
自动地。
我知道 formBuilder
将显式创建空心 FormControl
s 缺少 HTML 元素,但每个都有一个名称,在 HTML 中为 formControlName
给 HTML 元素一个名字,但没有 FormControl
实例,基本上是formControlName
和 formBuilder
两者都与匹配名称和填充空心的服务对话 FormControl
与其元素。
最后,FormControl
是验证者居住的地方,也是 dirty/touched/等等。特性。
我的问题 ngModel
和大家一样:validation sucks。自定义验证只不过是 if
的条件声明,但是 ngModel
希望我将那个小条件包装在整个指令中并将其粘贴在元素的 HTML 中。对于一个 if
,这是很多额外的输入声明——您不能使单行代码可重复使用,因为它需要一行来使用包装器。跨领域验证很糟糕。
我的问题 formBuilder
是赋值语句。对于一个有 12 个属性的模型,我写了 24 行,其中 12 行将值放入表单,12 行以类型不安全的方式将它们再次取出。这是 ngModel 不需要的大量额外输入,而且它有点违反 DRY 原则,因为我还必须在 Typescript 的 HTML 中重复输入字段的列表和层次结构。
最近我这样做:
<input type=text name=foo [(ngModel)]="myModel.myProperty" />
与
@ViewChild(NgModel) mod: NgModel;
ngAfterViewInit() {
this.mod.control.setValidators([Validators.required, Validators.minLength(3)]);
}
和
<span class=danger *ngIf="mod?.control?.errors?.required">....
这给了我两全其美的方式,简洁和控制。
但对于 <date-range-picker>
我发现我仍然必须使用 ControlValueAccessor
样板文件,这意味着我无法使用 ngModel
在其返回的小型 3 属性对象和我的官方 12 属性模型真实来源之间穿梭。需要三个显式赋值语句。我想避免这些,并避免更多特定于 Angular 样板。
如果 FormControl
会很容易选择器的 HTML 中的 s 可以使用选择器在 HTML 中看到 ngForm,但它不能。
我的问题是:FormControl
是如何产生的?用 NgForm
注册自己? FormBuilder
不需要 NgForm
作为输入参数,它“只知道”附加哪个表单。即使在同一个 HTML 模板中有多个表单,它也能正确处理。如果隐藏在它后面的服务可以找到 NgForm,我可以使用我的选择器中的该服务来找到它自己模板之外的 NgForm 吗?
最佳答案
FormControl
从 ngModel
/formControlName
构造函数接收 NgForm
实例,它由 Angular 放置在那里迪。组件装饰器中的这个“样板”:
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DateRangePickerComponent),
multi: true
}
...向 DI 系统注册自定义组件(实现 ControlValueAccessor
)。具体来说,NG_VALUE_ACCESSOR
扮演的 Angular 色与 PickerService
在这里扮演的 Angular 色相同:
export class MyComponent {
constructor(pickerService: PickerService)
multi: true
部分意味着注入(inject)的东西不仅仅是一个服务,就像 PickerService 一样,而是实际上是一组服务。 RadioControlValueAccessor
、SelectControlValueAccessor
和 CheckboxControlValueAccessor
位于此保护伞下,如果您使用,您自己的 DateRangePicker
可能也在其中“样板”。在查看 HTML 模板时,Angular 会为手头的工作选择正确的模板。
在 forwardRef
的 lambda 中包装一个组件只是解决了一个小的初始化顺序问题,仅此而已。
基本上,实现 ControlValueAccessor
会生成一个 Angular 期望的类,并且装饰器指定 Angular 中的位置来放置它。
但是如果你真的不想用它...
在父级 HTML 中的表单上使用模板引用 var,并将它传递给子组件,就像它是任何其他值一样:
<form #theForm="ngForm" ...
<date-range-picker [form]="theForm" ...
在子组件中,像任何其他输入一样接受表单,并获取对子 HTML 中使用的 ngModel 的引用(您已经为验证器目的完成了):
@Input() form: NgForm;
@ViewChild(NgModel) mod: NgModel;
命令式地把一个加到另一个。
ngAfterViewInit() {
this.mod.control.setValidators([Validators.required, c => c.value.duration != 0]);
this.form.addControl(this.mod);
}
你基本上就完成了。 *ngIf
可能会破坏并重新创建所述控件,或者更改检测不像通常那样彻底,但以这种方式解决这些问题意味着您正在有效地重新发明 Angular 。
当您在子模板中有多个 ngModel 时,这一点开始变得明显:
@ViewChildren(NgModel) ngModels: QueryList<NgModel>;
readonly validations = {
'reasonField': [Validators.required, Validators.maxLength(500)],
'durationField': [Validators.required, c => c.value.duration != 0],
};
ngAfterViewInit() {
this.ngModels.forEach(ngModel => {
ngModel.control.setValidators(this.validations[ngModel.name]);
this.form.addControl(ngModel);
});
}
..现在将验证绑定(bind)到字段开始看起来很像 formBuilder
了。 (没有 24 个赋值语句。)
关于angular - ngModel 但使用响应式(Reactive)验证方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51270454/