angular - ngModel 但使用响应式(Reactive)验证方法

标签 angular forms validation reactive ngmodel

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

我知道无论我是否去 formBuilderngModel路线,有一个NgForm对于表单,它有一个持 Root过的属性 FormGroup它具有 FormControl 的异构集合对象。 HTML 元素都有一个适配器对象实现 ControlValueAccessor界面,以及我自己的 Angular 组件,如 <date-range-picker>可以实现相同的接口(interface)并假装只是另一个元素,其值是任意复杂的对象。每个FormControl包装一个元素,通过 ControlValueAccessor 与它对话接口(interface),所以它不知道它到底在和什么对话。

我知道放置 ngModelformControl元素上的指令将创建 FormControl该元素的实例;该元素不会自动获得一个,即使是 <form>标签得到 NgForm自动地。

我知道 formBuilder将显式创建空心 FormControl s 缺少 HTML 元素,但每个都有一个名称,在 HTML 中为 formControlName给 HTML 元素一个名字,但没有 FormControl实例,基本上是formControlNameformBuilder两者都与匹配名称和填充空心的服务对话 FormControl与其元素。

最后,FormControl是验证者居住的地方,也是 dirty/touched/等等。特性。

我的问题 ngModel和大家一样:validation sucks。自定义验证只不过是 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 属性模型真实来源之间穿梭。需要三个显式赋值语句。我想避免这些,并避免更多特定于 Angular 样板。

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

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

最佳答案

FormControlngModel/formControlName 构造函数接收 NgForm 实例,它由 Angular 放置在那里迪。组件装饰器中的这个“样板”:

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

...向 DI 系统注册自定义组件(实现 ControlValueAccessor)。具体来说,NG_VALUE_ACCESSOR 扮演的 Angular 色与 PickerService 在这里扮演的 Angular 色相同:

export class MyComponent {
    constructor(pickerService: PickerService)

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

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

基本上,实现 ControlValueAccessor 会生成一个 Angular 期望的类,并且装饰器指定 Angular 中的位置来放置它。

但是如果你真的不想用它...

在父级 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);
    });
}

..现在将验证绑定(bind)到字段开始看起来很像 formBuilder 了。 (没有 24 个赋值语句。)

关于angular - ngModel 但使用响应式(Reactive)验证方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51270454/

相关文章:

jQuery hide() 不隐藏父级

css - 如何在html组合框中具有树状结构

php - Laravel 5 Form Requests - 验证相关数据

html - React JSX 文件的 W3C HTML 验证

单击后 Angular 禁用按钮?

angular - 将 observable 转换为 Promise 好吗?

javascript - 使用模型 [Angular] 的嵌套对象填充选择下拉列表

javascript - Angular8 充满了 HttpClient 响应头

javascript - 自动打开选择框

c# - 如果 XML 文件损坏,如何使用 C# 进行验证