简体   繁体   English

ngModel但使用反应形式验证方法

[英]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; 我知道在元素上放置ngModelformControl指令将为该元素创建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实例,并且基本上formControlNameformBuilder都与一个服务进行通信匹配名称并填充空心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. FormControlngModel / 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." RadioControlValueAccessorSelectControlValueAccessorCheckboxControlValueAccessor位于此保护伞下,如果您使用“样板”,您自己的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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM