[英]Bind template reference variables to ngModel while using reactive forms
[英]ngModel but using the reactive forms method of validation
我不喜欢Angular的形式验证路线,但我正在研究一种方法来结合每种方法的最佳方式,并且接近我喜欢的答案。
我知道无论我是使用formBuilder
还是ngModel
路由,都有一个表单的NgForm
,它有一个属性,包含根FormGroup
,它具有FormControl
对象的异构集合。 HTML Elements都有一个实现ControlValueAccessor
接口的适配器对象,我自己的角度组件如<date-range-picker>
可以实现相同的接口,并假装只是另一个元素,其值是一个任意复杂的对象。 每个FormControl
包装一个元素,通过ControlValueAccessor
接口与它进行对话,因此它ControlValueAccessor
它究竟与它实际交谈的内容。
我知道在元素上放置ngModel
或formControl
指令将为该元素创建FormControl
实例; 即使<form>
标签自动获得NgForm
,元素也不会自动获取。
我知道formBuilder
将显式创建缺少HTML元素的空心FormControl
,但每个都有一个名称,而在HTML中, formControlName
为HTML元素提供一个名称但没有FormControl
实例,并且基本上formControlName
和formBuilder
都与一个服务进行通信匹配名称并填充空心FormControl
及其元素。
最后, FormControl
是验证器所在的位置,以及脏/触摸/等。 属性。
我对ngModel
问题与每个人的问题相同:验证很糟糕。 自定义验证只不过是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属性模型源之间ngModel
值。真相。 需要三个显式赋值语句。 我想避免这些,并避免更多角度特定的样板。
如果选择器HTML中的FormControl
可以使用选择器在HTML中看到ngForm,那将很容易,但它不能。
我的问题是: FormControl
如何使用NgForm
注册自己? FormBuilder
不会将NgForm
作为输入参数,它只知道要附加哪种形式。 即使在同一个HTML模板中有多个表单,它也是正确的。 如果有一个服务隐藏在它后面并找到NgForm,我可以使用我的选择器中的那个服务来找到一个超出它自己的模板的NgForm吗?
FormControl
从ngModel
/ formControlName
构造函数接收NgForm
实例,该构造函数由Angular DI放置在那里。 组件装饰器中的这个“样板”:
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DateRangePickerComponent),
multi: true
}
...使用DI系统注册自定义组件(正在实施ControlValueAccessor
)。 具体来说, NG_VALUE_ACCESSOR
扮演的角色与PickerService
在此处扮演的角色相同:
export class MyComponent {
constructor(pickerService: PickerService)
multi: true
部分意味着注入的东西不仅仅是一个服务,就像PickerService一样,但实际上是一组服务。 RadioControlValueAccessor
, SelectControlValueAccessor
和CheckboxControlValueAccessor
位于此保护伞下,如果您使用“样板”,您自己的DateRangePicker
可能就在其中。 在查看HTML模板时,Angular会为当前作业选择正确的一个。
将一个组件包装在lambda中用于forwardRef
只是解决了一个小的初始化顺序问题,仅此而已。
基本上,实现ControlValueAccessor
使一类什么角度预计,和装饰指定了在角把它。
但如果你真的不想用它......
在父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);
});
}
..现在将验证绑定到字段开始看起来很像formBuilder
。 (没有24个赋值语句。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.