Angular自定義表單驗證器 - 怎樣實現“再次輸入密碼”的驗證(兩個controller值相等)(equalTo) ...
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
}
};
};
}
}