[英]ngModel but using the reactive forms method of validation
I don't like either of Angular's form validation routes, but I'm investigating a way to combine the best of each, and am close to an answer I like. 我不喜欢Angular的形式验证路线,但我正在研究一种方法来结合每种方法的最佳方式,并且接近我喜欢的答案。
I know that regardless whether I go the formBuilder
or the ngModel
route, there is an NgForm
for the form, which has a property holding the root FormGroup
which has a heterogeneous collection of FormControl
objects. 我知道无论我是使用
formBuilder
还是ngModel
路由,都有一个表单的NgForm
,它有一个属性,包含根FormGroup
,它具有FormControl
对象的异构集合。 HTML Elements all have an adapter object implementing the ControlValueAccessor
interface, and my own angular components like <date-range-picker>
can implement the same interface and pretend to be just-another-element whose value is an arbitrarily complex object. HTML Elements都有一个实现
ControlValueAccessor
接口的适配器对象,我自己的角度组件如<date-range-picker>
可以实现相同的接口,并假装只是另一个元素,其值是一个任意复杂的对象。 Each FormControl
wraps an element, talking with it via the ControlValueAccessor
interface so it is agnostic as to what, exactly, it is actually talking to. 每个
FormControl
包装一个元素,通过ControlValueAccessor
接口与它进行对话,因此它ControlValueAccessor
它究竟与它实际交谈的内容。
I know that placing either the ngModel
or formControl
directive on an element will create the FormControl
instance for that element; 我知道在元素上放置
ngModel
或formControl
指令将为该元素创建FormControl
实例; the element doesn't get one automatically, even though a <form>
tag gets an NgForm
automatically. 即使
<form>
标签自动获得NgForm
,元素也不会自动获取。
I know that formBuilder
will explicitly create hollow FormControl
s which lack the HTML element, but each has a name, and in the HTML the formControlName
gives the HTML elements a name but no FormControl
instance, and basically formControlName
and formBuilder
both talk to a service that matches the names and fills-in the hollow FormControl
with its element. 我知道
formBuilder
将显式创建缺少HTML元素的空心FormControl
,但每个都有一个名称,而在HTML中, formControlName
为HTML元素提供一个名称但没有FormControl
实例,并且基本上formControlName
和formBuilder
都与一个服务进行通信匹配名称并填充空心FormControl
及其元素。
Finally, FormControl
is where the validators live, as well as the dirty/touched/etc. 最后,
FormControl
是验证器所在的位置,以及脏/触摸/等。 properties. 属性。
My issue with ngModel
is the same as everyone's: validation sucks. 我对
ngModel
问题与每个人的问题相同:验证很糟糕。 A custom validation is little more than the condition of an if
statement, but ngModel
wants me to wrap that little condition in an entire directive and stick it in the HTML on the element. 自定义验证只不过是
if
语句的条件,但是ngModel
希望我将整个指令中的那个小条件包装起来并将其粘贴在元素的HTML中。 That's a lot of extra typing for an if
statement -- you can't make a one-liner re-usable because it takes one line to use the wrapper. 对于
if
语句来说,这是一个额外的输入 - 你不能使单行重用,因为它需要一行来使用包装器。 And cross-field validation sucks. 并且跨领域验证很糟糕。
My issue with formBuilder
is the assignment statements. 我对
formBuilder
问题是赋值语句。 For a model of 12 properties I'm writing 24 lines, 12 to put the values into the form and 12 to get them back out again, in a type-unsafe manner. 对于12个属性的模型,我写了24行,12个将值放入表单中,12个以类型不安全的方式再次将它们取回。 That's a lot of extra typing that ngModel didn't require, and it kinda violates the DRY principle since I have to repeat the list and hierarchy of input fields in the HTML within the Typescript as well.
这是ngModel不需要的大量额外输入,并且它违反了DRY原则,因为我必须在Typescript中的HTML中重复输入字段的列表和层次结构。
Lately I do this: 我最近这样做:
<input type=text name=foo [(ngModel)]="myModel.myProperty" />
with 同
@ViewChild(NgModel) mod: NgModel;
ngAfterViewInit() {
this.mod.control.setValidators([Validators.required, Validators.minLength(3)]);
}
and 和
<span class=danger *ngIf="mod?.control?.errors?.required">....
This gives me the best of both worlds, concision and control. 这给了我两全其美,简洁和控制。
But for <date-range-picker>
I find I still have to use the ControlValueAccessor
boilerplate, which means I then cannot use ngModel
to shuttle values between its small 3-property object being returned and my official 12-property model source-of-truth. 但对于
<date-range-picker>
我发现我仍然需要使用ControlValueAccessor
样板,这意味着我不能使用ngModel
在它返回的小3属性对象和我的官方12属性模型源之间ngModel
值。真相。 Three explicit assignment statements are needed. 需要三个显式赋值语句。 I want to avoid those, and avoid more angular-specific boilerplate.
我想避免这些,并避免更多角度特定的样板。
It would be easy if the FormControl
s inside the picker's HTML could see the ngForm in the HTML using the picker, but it can't. 如果选择器HTML中的
FormControl
可以使用选择器在HTML中看到ngForm,那将很容易,但它不能。
My question is: how does a FormControl
register itself with an NgForm
? 我的问题是:
FormControl
如何使用NgForm
注册自己? FormBuilder
doesn't take an NgForm
as an input parameter, it 'just knows' which form to attach. FormBuilder
不会将NgForm
作为输入参数,它只知道要附加哪种形式。 Even if there's multiple forms in the same HTML template, it gets it right. 即使在同一个HTML模板中有多个表单,它也是正确的。 If there's a service hiding behind it that goes and finds the NgForm, can I use that service from my picker to find an NgForm that is outside of its own template?
如果有一个服务隐藏在它后面并找到NgForm,我可以使用我的选择器中的那个服务来找到一个超出它自己的模板的NgForm吗?
FormControl
s receive the NgForm
instance from the ngModel
/ formControlName
constructor, which is placed there by Angular DI. FormControl
从ngModel
/ formControlName
构造函数接收NgForm
实例,该构造函数由Angular DI放置在那里。 This "boilerplate" in a component's decorator: 组件装饰器中的这个“样板”:
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DateRangePickerComponent),
multi: true
}
...registers a custom component (which is implementing ControlValueAccessor
) with the DI system. ...使用DI系统注册自定义组件(正在实施
ControlValueAccessor
)。 Specifically, NG_VALUE_ACCESSOR
plays the same role as the PickerService
does here: 具体来说,
NG_VALUE_ACCESSOR
扮演的角色与PickerService
在此处扮演的角色相同:
export class MyComponent {
constructor(pickerService: PickerService)
The multi: true
part means the injected thing isn't just one service, like the PickerService is, but is actually a group of them. multi: true
部分意味着注入的东西不仅仅是一个服务,就像PickerService一样,但实际上是一组服务。 RadioControlValueAccessor
, SelectControlValueAccessor
, and CheckboxControlValueAccessor
sit under this umbrella, and your own DateRangePicker
could be among them if you use the "boilerplate." RadioControlValueAccessor
, SelectControlValueAccessor
和CheckboxControlValueAccessor
位于此保护伞下,如果您使用“样板”,您自己的DateRangePicker
可能就在其中。 Angular selects the correct one for the job at hand when looking through an HTML template. 在查看HTML模板时,Angular会为当前作业选择正确的一个。
Wrapping a component in a lambda for forwardRef
just solves a small order-of-initialization issue, nothing more. 将一个组件包装在lambda中用于
forwardRef
只是解决了一个小的初始化顺序问题,仅此而已。
Basically, implementing ControlValueAccessor
makes a class what Angular expects, and the decorator specifies where in Angular to put it. 基本上,实现
ControlValueAccessor
使一类什么角度预计,和装饰指定了在角把它。
But if you really don't want to use it... 但如果你真的不想用它......
Use a template reference var on the form in the parent's HTML, and pass it to child component like it was any other value: 在父HTML的表单上使用模板引用var,并将其传递给子组件,就像它是任何其他值一样:
<form #theForm="ngForm" ...
<date-range-picker [form]="theForm" ...
In the child component, accept the form like any other input, and also grab a reference to the ngModel used in the child's HTML (which you've already done for validator purposes): 在子组件中,像任何其他输入一样接受表单,并且还获取对子HTML(您已经为验证器目的而做)中使用的ngModel的引用:
@Input() form: NgForm;
@ViewChild(NgModel) mod: NgModel;
Add one to the other imperatively. 强制性地将一个添加到另一个。
ngAfterViewInit() {
this.mod.control.setValidators([Validators.required, c => c.value.duration != 0]);
this.form.addControl(this.mod);
}
And you're basically done. 你基本上完成了。 There may be issues with
*ngIf
destroying and re-creating said control, or change detection not being as thorough as it normally would be, but solving these issues in this way means you're effectively re-inventing Angular. *ngIf
可能存在破坏和重新创建所述控件的问题,或者更改检测不像通常那样彻底,但以这种方式解决这些问题意味着您正在有效地重新发明Angular。
This starts to become apparent when you have multiple ngModels in a child's template: 当您在子模板中有多个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);
});
}
..and now tying the validations to the fields starts to look a lot like formBuilder
all over again. ..现在将验证绑定到字段开始看起来很像
formBuilder
。 (Without the 24 assignment statements.) (没有24个赋值语句。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.