Angular NGM模型,但使用反应形式验证方法

Angular NGM模型,但使用反应形式验证方法,angular,forms,validation,reactive,ngmodel,Angular,Forms,Validation,Reactive,Ngmodel,我不喜欢Angular的任何一种表单验证方法,但我正在研究一种将每种方法的优点结合起来的方法,并且接近我喜欢的答案 我知道,无论我选择formBuilder还是ngModel路线,表单都有一个NgForm,它有一个包含根FormGroup的属性,该根FormGroup具有FormControl对象的异构集合。HTML元素都有一个adapter对象来实现ControlValueAccessor接口,我自己的角度组件,如可以实现相同的接口,并假装只是另一个元素,其值是任意复杂的对象。每个FormCo

我不喜欢Angular的任何一种表单验证方法,但我正在研究一种将每种方法的优点结合起来的方法,并且接近我喜欢的答案

我知道,无论我选择
formBuilder
还是
ngModel
路线,表单都有一个
NgForm
,它有一个包含根
FormGroup
的属性,该根
FormGroup
具有
FormControl
对象的异构集合。HTML元素都有一个adapter对象来实现
ControlValueAccessor
接口,我自己的角度组件,如
可以实现相同的接口,并假装只是另一个元素,其值是任意复杂的对象。每个
FormControl
包装一个元素,通过
ControlValueAccessor
接口与它对话,因此它不知道它实际上在与什么对话

我知道在元素上放置
ngModel
formControl
指令将为该元素创建
formControl
实例;即使
标记自动获取
NgForm,元素也不会自动获取一个

我知道
formBuilder
会显式创建空心
FormControl
s,它缺少HTML元素,但每个都有一个名称,在HTML中
formControlName
给HTML元素一个名称,但没有
FormControl
实例,基本上,
formControlName
formBuilder
都与一个与名称匹配的服务对话,并用其元素填充
FormControl
中的空洞

最后,
FormControl
是验证器所在的位置,以及dirty/toucted/etc.属性

我对
ngModel
的问题和大家的一样:验证很糟糕。自定义验证只不过是一个
if
语句的条件,但是
ngModel
希望我将这个小条件包装在一个完整的指令中,并将其粘贴到元素的HTML中。对于
if
语句来说,这需要大量额外的输入——您无法使一行代码可重用,因为使用包装器需要一行代码。跨领域验证很糟糕

我对formBuilder的问题是赋值语句。对于一个包含12个属性的模型,我写了24行,12行用于将值放入表单中,12行用于以一种类型不安全的方式再次取出它们。这是ngModel不需要的大量额外键入,而且它有点违反了DRY原则,因为我必须在Typescript中重复HTML中输入字段的列表和层次结构

最近我这样做:

<input type=text name=foo [(ngModel)]="myModel.myProperty" />

。。。。
这给了我最好的两个世界,简洁和控制

但是对于
我发现我仍然必须使用
ControlValueAccessor
样板文件,这意味着我不能使用
ngModel
在返回的小3属性对象和我的官方12属性模型真值源之间传递值。需要三个明确的赋值语句。我想避免这些,并避免更多角度特定的样板

如果选取器HTML中的
FormControl
s可以使用选取器在HTML中看到ngForm,这将很容易,但它不能


我的问题是:
FormControl
如何向
NgForm
注册自身
FormBuilder
不将
NgForm
作为输入参数,它“只知道”要附加哪个表单。即使在同一个HTML模板中有多个表单,它也能正确处理。如果有一个隐藏在它后面的服务找到了NgForm,我可以从我的选择器中使用该服务来找到它自己模板之外的NgForm吗?

FormControl
s从
ngModel
/
formControlName
构造函数接收
NGFormForm
实例,该构造函数由Angular DI放置在那里。组件装饰器中的“样板文件”:

{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => DateRangePickerComponent),
    multi: true
}
…向DI系统注册自定义组件(正在实现
ControlValueAccessor
)。具体而言,
NG\u VALUE\u访问器
PickerService
在这里的作用相同:

export class MyComponent {
    constructor(pickerService: PickerService)
multi:true
部分意味着注入的东西不只是一个服务,就像PickerService一样,而是一组服务
RadioControlValueAccessor
SelectControlValueAccessor
,和
CheckboxControlValueAccessor
坐在这把伞下,如果你使用“样板文件”,你自己的
DateRangePicker
可能就是其中之一。Angular在查看HTML模板时为手头的工作选择了正确的一个

将组件包装在lambda for
forwardRef
中只解决了一小部分初始化问题,仅此而已

基本上,实现
ControlValueAccessor
可以生成Angular期望的类,并且装饰器指定Angular中的位置

但是如果你真的不想使用它…

在父HTML中的表单上使用模板引用变量,并将其传递给子组件,就像传递任何其他值一样:

<form #theForm="ngForm" ...
    <date-range-picker [form]="theForm" ...
必须将一个添加到另一个

ngAfterViewInit() {
    this.mod.control.setValidators([Validators.required, c => c.value.duration != 0]);
    this.form.addControl(this.mod);
}
你基本上完成了。
*ngIf
销毁和重新创建所述控件可能会有问题,或者更改检测不像通常那样彻底,但以这种方式解决这些问题意味着您正在有效地重新发明

当一个子模板中有多个ngModels时,这一点开始变得明显:

@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);
    });
}
@ViewChildren(NgModel)ngModels:QueryList;
只读验证={
'reasonField':[Validators.required,Validators.maxLength(500)],
'durationField':[Validators.required,c=>c.value.duration!=0],
};
ngAfterViewInit(){
这个.ngModels.forEach(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);
}
@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);
    });
}