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

时间:2018-07-10 16:44:46

标签: angular forms validation reactive ngmodel

我不喜欢Angular的任何一种表单验证路径,但是我正在研究一种将每种方法的最佳组合在一起的方法,并且接近我喜欢的答案。

我知道,无论我走formBuilder还是走ngModel路线,表单都有一个NgForm,该属性具有保存根FormGroup的属性,具有FormControl个对象的异构集合。 HTML元素都有一个实现ControlValueAccessor接口的适配器对象,而我自己的<date-range-picker>之类的角度组件可以实现相同的接口,并假装为另一个元素,其值是一个任意复杂的对象。每个FormControl都会包装一个元素,并通过ControlValueAccessor接口与之交谈,因此它无法确切知道实际上在与谁交谈。

我知道在元素上放置ngModelformControl指令将为该元素创建FormControl实例;即使<form>标签自动获取了NgForm,元素也不会自动获取。

我知道formBuilder将显式创建空心FormControl,它们缺少HTML元素,但每个都有一个名称,在HTML中,formControlName给HTML元素命名,但没有FormControl实例,并且基本上formControlNameformBuilder都与匹配名称的服务通信,并使用其元素填充空心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属性模型真相。需要三个明确的赋值语句。我想避免这些,并避免使用更多特定于角度的样板。

如果选择器的HTML中的FormControl可以使用选择器看到HTML中的ngForm,这很容易,但不能做到。

我的问题是:FormControl如何向NgForm注册? FormBuilder并没有接受NgForm作为输入参数,它只是知道要附加的形式。即使同一HTML模板中有多种形式,它也能正确处理。如果有一个隐藏在其后面的服务可以找到NgForm,我可以使用我的选择器中的该服务来查找其模板之外的NgForm吗?

1 个答案:

答案 0 :(得分:1)

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

{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => DateRangePickerComponent),
    multi: true
}

...向DI系统注册自定义组件(正在实现ControlValueAccessor)。具体来说,NG_VALUE_ACCESSORPickerService的作用相同:

export class MyComponent {
    constructor(pickerService: PickerService)

multi: true部分意味着注入的东西不仅像PickerService一样是一个服务,而且实际上是一组服务。 RadioControlValueAccessorSelectControlValueAccessorCheckboxControlValueAccessor坐在这把伞下,如果您使用“样板”,您自己的DateRangePicker可能会在其中。 Angular在查看HTML模板时会为手头的工作选择正确的选项。

在lambda中为forwardRef包装一个组件只会解决一个小的初始化顺序问题,仅此而已。

基本上,实现ControlValueAccessor会使Angular期望一个类 ,装饰器在Angular中指定 where 来放置它。

但是,如果您真的不想使用它...

在父级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个赋值语句。)