zoukankan      html  css  js  c++  java
  • Angular5+ 自定义表单验证器

    Angular5+ 自定义表单验证器

    Custom Validators

    标签(空格分隔): Angular


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

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

    解决思路:

    • 第一个问题,可以通过[AbstractControl].root.get([targetName])来取得指定的controller,然后比较他们的值。
    • 第二个,可以通过[target].setErrors([errors])来实现。
    1. 这是一个我的自定义表单验证:
    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等的是全局变量。

    1. 然后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动画效果。

    1. 然后在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
            }
          };
        };
      }
    }
    
  • 相关阅读:
    JAVA CookieUtil
    Maven打包时提示No runnable methods的解决方案
    Spring MVC中Junit测试简单讲解
    C# 连接MongoDB,含用户验证
    Spring中同一个service中方法相互调用事务不生效问题解决方案
    004. vue html模板字符串转为ast(js对象)
    03事件模型-发布订阅模式
    000 学习vue源码里面一些处理技巧
    02 响应式原理
    my-ts-axios
  • 原文地址:https://www.cnblogs.com/jehorn/p/9687501.html
Copyright © 2011-2022 走看看