好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Angular5+ 自定义表单验证器

Angular5+ 自定义表单验证器

Custom Validators

标签(空格分隔): Angular

首先阐述一下遇到的问题:

怎样实现“再次输入密码”的验证(两个controller值相等)(equalTo) 怎样反向监听(先输入“再次输入密码”,后输入设置密码)

解决思路:

第一个问题,可以通过 [AbstractControl].root.get([targetName]) 来取得指定的controller,然后比较他们的值。 第二个,可以通过 [target].setErrors([errors]) 来实现。 这是一个我的自定义表单验证:
 import {AbstractControl, FormGroup, ValidatorFn} from \'@angular/forms\';
import {G} from \'services/data-store.service\';

export class MyValidators {
  private static isEmptyInputValue(value) {
    // we don\'t check for string here so it also works with arrays
    return value == null || value.length === 0;
  }
  private static isEmptyObject(obj) {
    if (typeof obj === \'object\' && typeof obj.length !== \'number\') {
      return Object.keys(obj).length === 0;
    }
    return null;
  }

  /**
   * 等于指定controller的值
   * @param targetName 目标的formControlName
   * @returns {(ctrl: FormControl) => {equalTo: {valid: boolean}}}
   */
  static equalTo(targetName: string): ValidatorFn {
    return (control: AbstractControl): {[key: string]: any} | null => {
      const target = control.root.get(targetName);
      if (target === null) {
        return null;
      }
      if (this.isEmptyInputValue(control.value)) {
        return null;
      }
      return target.value === control.value ? null : {\'equalto\': { valid: false }};
    };
  }

  /**
   * 反向输入监听指定controller是否与当前值相等
   * @param targetName
   */
  static equalFor(targetName: string): ValidatorFn {
    return (control: AbstractControl): {[key: string]: any} | null => {
      const target = control.root.get(targetName);
      if (target === null) {
        return null;
      }
      if (this.isEmptyInputValue(control.value)) {
        return null;
      }
      if (target.value === control.value) {
        const errors = target.errors;
        delete errors[\'equalto\'];

        if (this.isEmptyObject(errors)) {
          target.setErrors(null);
        } else {
          target.setErrors(errors);
        }
        return null;
      }
      target.setErrors({ \'equalto\': { valid: false } });
    };
  }

  ...
}
 

(注:)其中 G.REGEX 等的是全局变量。

然后 FormBuilder 来实现:
 import { Component, OnInit } from \'@angular/core\';
import {EventsService} from \'services/events.service\';
import {FormBuilder, FormGroup, Validators} from \'@angular/forms\';
import {G} from \'services/data-store.service\';
import {fade} from \'animations/fade.animation\';
import {MyValidators} from \'directives/my-validators.directive\';

@Component({
  selector: \'app-sign-up\',
  templateUrl: \'./sign-up.component.html\',
  styleUrls: [\'./sign-up.component.scss\'],
  animations: [fade]
})
export class SignUpComponent implements OnInit {
  signForm: FormGroup; // 表单组FormGroup
  submitting: boolean; // 是否可以提交
  validations = G.VALIDATIONS;

  constructor(private eventsService: EventsService, private formBuilder: FormBuilder) {
    this.submitting = false;

    // 
    this.init();
  }

  ngOnInit() {
    // 设置父组件标题
    this.eventsService.publish(\'setSign\', { title: \'注册\', subTitle: { name: \'立即登录\', uri: \'/account/sign-in\' } });
  }

  // 立即注册
  onSubmit() {
    console.log(this.signForm.getRawValue());
  }

  // 表单初始化
  private init() {
    this.signForm = this.formBuilder.group({
      username: [\'\', Validators.compose([Validators.required, Validators.maxLength(this.validations.USR_MAX)])],
      password: [\'\', Validators.compose([
        Validators.required,
        Validators.minLength(this.validations.PASS_MIN),
        Validators.maxLength(this.validations.PASS_MAX),
        MyValidators.equalFor(\'passwordConfirm\')
      ])],
      passwordConfirm: [\'\', Validators.compose([
        Validators.required,
        Validators.minLength(this.validations.PASS_MIN),
        Validators.maxLength(this.validations.PASS_MAX),
        MyValidators.equalTo(\'password\')
      ])]
    });
  }
}
 

(注:)其中 fade 动画效果。

然后在html模板中,显示表单验证提示信息:
 <form [formGroup]="signForm" (ngSubmit)="onSubmit()" class="sign-form" @fade>

  <!-- 账号 -->
  <div class="input-group username">
    <span class="addon prev"><i class="civ civ-i-usr"></i></span>
    <input type="text"
      name="username"
      class="form-control form-control-left default"
      placeholder="请输入账号"
      formControlName="username"
      autocomplete="off">
    <ul class="errors" *ngIf="signForm.get(\'username\').invalid && (signForm.get(\'username\').dirty || signForm.get(\'username\').touched)">
      <li *ngIf="signForm.get(\'username\').hasError(\'required\')" class="error">
        请输入您的账号!
      </li>
      <li *ngIf="signForm.get(\'username\').hasError(\'maxlength\')" class="error">
        账号不超过{{ validations.USR_MAX }}位!
      </li>
    </ul>
  </div> <!-- /.账号 -->
  
  <!-- 密码 -->
  <div class="input-group password">
    <span class="addon prev"><i class="civ civ-i-lock"></i></span>
    <input type="password"
      name="password"
      class="form-control form-control-left default"
      placeholder="请输入密码"
      formControlName="password">
    <ul class="errors" *ngIf="signForm.get(\'password\').invalid && (signForm.get(\'password\').dirty || signForm.get(\'password\').touched)">
      <li *ngIf="signForm.get(\'password\').hasError(\'required\')" class="error">
        请输入您的密码!
      </li>
      <li *ngIf="signForm.get(\'password\').hasError(\'minlength\')" class="error">
        请输入至少{{ validations.PASS_MIN }}位数的密码!
      </li>
      <li *ngIf="signForm.get(\'password\').hasError(\'maxlength\')" class="error">
        密码不超过{{ validations.PASS_MAX }}位!
      </li>
    </ul>
  </div> <!-- /.密码 -->
  
  <!-- 重复密码 -->
  <div class="input-group password-confirm">
    <span class="addon prev"><i class="civ civ-i-lock"></i></span>
    <input type="password"
           name="passwordConfirm"
           class="form-control form-control-left default"
           placeholder="请再次输入密码"
           formControlName="passwordConfirm">
    <ul class="errors" *ngIf="signForm.get(\'passwordConfirm\').invalid && (signForm.get(\'passwordConfirm\').dirty || signForm.get(\'passwordConfirm\').touched)">
      <li *ngIf="signForm.get(\'passwordConfirm\').hasError(\'required\')" class="error">
        请再次输入密码!
      </li>
      <li *ngIf="signForm.get(\'passwordConfirm\').hasError(\'minlength\')" class="error">
        请输入至少{{ validations.PASS_MIN }}位数的密码!
      </li>
      <li *ngIf="signForm.get(\'passwordConfirm\').hasError(\'maxlength\')" class="error">
        密码不超过{{ validations.PASS_MAX }}位!
      </li>
      <li *ngIf="!signForm.get(\'passwordConfirm\').hasError(\'maxlength\') && !signForm.get(\'passwordConfirm\').hasError(\'minlength\') && signForm.get(\'passwordConfirm\').hasError(\'equalto\')" class="error">
        两次密码输入不一致!
      </li>
    </ul>
  </div> <!-- /.重复密码 -->
  
  <!-- 提交按钮 -->
  <button type="submit"
          class="btn btn-primary btn-block submit"
          [disabled]="submitting || signForm.invalid">立即注册</button>
  <!-- /.提交按钮 -->
  
</form>
 

最后,我们可以看到,实现了想要的效果:






(附:)完整的自定义表单验证器:

 import {AbstractControl, FormGroup, ValidatorFn} from \'@angular/forms\';
import {G} from \'services/data-store.service\';

export class MyValidators {
  private static isEmptyInputValue(value) {
    // we don\'t check for string here so it also works with arrays
    return value == null || value.length === 0;
  }
  private static isEmptyObject(obj) {
    if (typeof obj === \'object\' && typeof obj.length !== \'number\') {
      return Object.keys(obj).length === 0;
    }
    return null;
  }

  /**
   * 等于指定controller的值
   * @param targetName 目标的formControlName
   * @returns {(ctrl: FormControl) => {equalTo: {valid: boolean}}}
   */
  static equalTo(targetName: string): ValidatorFn {
    return (control: AbstractControl): {[key: string]: any} | null => {
      const target = control.root.get(targetName);
      if (target === null) {
        return null;
      }
      if (this.isEmptyInputValue(control.value)) {
        return null;
      }
      return target.value === control.value ? null : {\'equalto\': { valid: false }};
    };
  }

  /**
   * 反向输入监听指定controller是否与当前值相等
   * @param targetName
   */
  static equalFor(targetName: string): ValidatorFn {
    return (control: AbstractControl): {[key: string]: any} | null => {
      const target = control.root.get(targetName);
      if (target === null) {
        return null;
      }
      if (this.isEmptyInputValue(control.value)) {
        return null;
      }
      if (target.value === control.value) {
        const errors = target.errors;
        delete errors[\'equalto\'];

        if (this.isEmptyObject(errors)) {
          target.setErrors(null);
        } else {
          target.setErrors(errors);
        }
        return null;
      }
      target.setErrors({ \'equalto\': { valid: false } });
    };
  }

  /**
   * 验证手机号
   * @returns {(ctrl: FormControl) => {mobile: {valid: boolean}}}
   */
  static get mobile() {
    return (control: AbstractControl) => {
      if (this.isEmptyInputValue(control.value)) {
        return null;
      }

      const valid = G.REGEX.MOBILE.test(control.value);

      return valid ? null : {
        \'mobile\': {
          valid: false
        }
      };
    };
  }

  /**
   * 验证身份证
   * @returns {(ctrl: FormControl) => {idCard: {valid: boolean}}}
   */
  static get idCard() {
    return (control: AbstractControl) => {
      if (this.isEmptyInputValue(control.value)) {
        return null;
      }

      const valid = G.REGEX.ID_CARD.test(control.value);

      return valid ? null : {
        \'idcard\': {
          valid: false
        }
      };
    };
  }

  /**
   * 验证汉字
   * @returns {(ctrl: FormControl) => {cn: {valid: boolean}}}
   */
  static get cn() {
    return (control: AbstractControl) => {
      if (this.isEmptyInputValue(control.value)) {
        return null;
      }

      const valid = G.REGEX.CN.test(control.value);

      return valid ? null : {
        \'cn\': {
          valid: false
        }
      };
    };
  }

  /**
   * 指定个数数字
   * @param {number} length
   * @returns {(ctrl: FormControl) => (null | {number: {valid: boolean}})}
   */
  static number(length: number = 6) {
    return (control: AbstractControl) => {
      if (this.isEmptyInputValue(control.value)) {
        return null;
      }

      const valid = new RegExp(`^\\d{${length}}$`).test(control.value);

      return valid ? null : {
        \'number\': {
          valid: false
        }
      };
    };
  }

  /**
   * 强密码(必须包含数字字母)
   * @returns {(ctrl: FormControl) => (null | {number: {valid: boolean}})}
   */
  static get strictPass() {
    return (control: AbstractControl) => {
      if (this.isEmptyInputValue(control.value)) {
        return null;
      }

      const valid = G.REGEX.STRICT_PASS.test(control.value);

      return valid ? null : {
        \'strictpass\': {
          valid: false
        }
      };
    };
  }
}
 

查看更多关于Angular5+ 自定义表单验证器的详细内容...

  阅读:34次