Angular2 ngModel验证器就在组件中

gga*_*bor 4 validation angular2-forms angular

我试图找出为ngModel实现自定义验证器逻辑的最简单方法.我有一个预定义的模型(接口),它存储当前数据,所以我不想处理新的FormGroup/FormControl(模型驱动)方法.

如果我已经拥有了我需要的所有数据,为什么要使用FormControls构建完全相同的模式?

这是我的代码(https://plnkr.co/edit/fPEdbMihRSVqQ5LZYBHO):

import { Component, Input } from '@angular/core';


export interface MyWidgetModel {
  title:string;
  description:string;
}


@Component({
  selector: 'my-widget',
  template: `
    <h4 *ngIf="!editing">{{data.title}}</h4>
    <input *ngIf="editing" type="text" name="title" [(ngModel)]="data.title">

    <p *ngIf="!editing">{{data.description}}</p>
    <textarea *ngIf="editing" name="description" [(ngModel)]="data.description" (ngModelChange)="customValidator($event)"></textarea>

    <button (click)="clickEditing()">{{editing ? 'save' : 'edit'}}</button>

  `
  styles: [
    ':host, :host > * { display: block; margin: 5px; }',
    ':host { margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #eee; }',
    '.ng-invalid { background-color: #FEE; }'
  ]

})
export class MyWidgetComponent {
  @Input() data:MyWidgetModel;

  constructor() {
    this.editing = false;
  }

  clickEditing() {
    this.editing = !this.editing;
  }

  customValidator(value:string) {
    console.log(this, value); //should be: MyWidgetComponent
    //How to set 'invalid' state here?
  }

}
Run Code Online (Sandbox Code Playgroud)

如您所见,我可以快速打开/关闭编辑模式,我可以直接编辑我的数据.

我的问题是如何直接从我的组件管理ngModel的ng-valid/ng-invalid状态?这背后的想法包含多个要点:

  • 为什么我们应该为FormGroups创建一个新的局部变量 - 具有相同的结构 - 当数据模型已经存在时FormControls?
  • 组件本身实现了所需的业务逻辑,因此所有业务规则验证器也必须在此处实现.
  • 可能有许多复杂的验证逻辑.这些不能仅使用输入的纯文本值和所需的简单检查,长度,模式等来实现.
  • 由于以上所述,我认为我们最终需要整个组件对象来解决所有真实​​的业务规则验证.

gga*_*bor 7

最后我想出了办法.我认为这是最简单的.我还更新了plunker:https://plnkr.co/edit/fPEdbMihRSVqQ5LZYBHO

让我们一步一步看.

1 - 创建一个简单的最小指令,实现Validator接口 - 与往常一样 - 但不要编写任何验证逻辑.而是提供函数类型的Input()字段 - 与选择器同名.这将允许我们在此验证器之外实现真正的逻辑.在validate(...)函数内部只调用外部Input()函数.

import { Directive, forwardRef, Input } from '@angular/core';
import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn } from '@angular/forms';

@Directive({
  selector: '[myvalidator][ngModel],[myvalidator][ngFormControl]',
  providers: [{
    multi: true,
    provide: NG_VALIDATORS, 
    useExisting: forwardRef(() => MyValidator)      
  }]
})
export class MyValidator implements Validator {
  @Input() myvalidator:ValidatorFn; //same name as the selector

  validate(control: AbstractControl):{ [key: string]: any; } {
    return this.myvalidator(control);
  }

}
Run Code Online (Sandbox Code Playgroud)

2 - 要使用自定义验证器,只需将其导入并添加到组件的指令数组中.在模板标记中使用它像任何其他指令:

<input type="text" name="title" [(ngModel)]="data.title" [myvalidator]="validateTitle()">
Run Code Online (Sandbox Code Playgroud)

诀窍就在这里.传递给验证器的Input()函数的值是函数调用 - 它将返回验证器函数.这里是:

validateTitle() {
    return <ValidatorFn>((control:FormControl) => {

      //implement a custom validation logic here.
      //the 'this' points the component instance here thanks to the arrow syntax.

      return null; //null means: no error.
  });
Run Code Online (Sandbox Code Playgroud)

以上所有与官方Angular2验证器完全兼容 - 必需,模式等 - 因此我们的自定义验证器可以在没有任何进一步技巧的情况下进行组合.

编辑: 如果在组件的构造函数中为每个验证创建局部变量,则可以实现更简单有效的方法:

private validateTitle:ValidatorFn;

constructor() {
  this.validateTitle = (control:FormControl) => {

      //implement a custom validation logic here.
      //the 'this' points the component instance here thanks to the arrow syntax.

      return null; //null means: no error.
  };
}
Run Code Online (Sandbox Code Playgroud)

使用这种方法,我们只创建一次ValidatorFn函数,而不是每个验证请求.1个函数调用eleminated:validateTitle().所以在模板中我们可以绑定我们的变量:

<input type="text" name="title" [(ngModel)]="data.title" [myvalidator]="validateTitle">
Run Code Online (Sandbox Code Playgroud)