组件中的 Angular2 ngModel 验证器

Angular2 ngModel validator just in the component

我试图找出为 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?
  }

}

如您所见,我可以快速打开 on/off 编辑模式,我可以直接就地编辑我的数据。

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

终于想到办法了。我认为这是最简单的。 我还更新了插件: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);
  }

}

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

<input type="text" name="title" [(ngModel)]="data.title" [myvalidator]="validateTitle()">

诀窍就在这里。传递给验证器的 Input() 函数的值是一个函数调用 - 这将 returns 一个验证器函数。这是:

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.
  });

以上所有与官方 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.
  };
}

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

<input type="text" name="title" [(ngModel)]="data.title" [myvalidator]="validateTitle">

如果您不想使用一次性模板驱动的表单验证指令:

使输入可访问

#receiverInput="ngModel"

在控制器中绑定

@ViewChild(NgModel, { static: true }) receiverInput: NgModel;

验证

this.receiverInput.control.setValidators((control: AbstractControl) => {
  if (!this.receiver.kundenNr) {
    // invalid
    return { receiver: false };
  }
  // valid
  return null;
});