標籤(空格分隔): Angularcss
首先闡述一下遇到的問題:html
解決思路:app
[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
動畫效果。ui
<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>
最後,咱們能夠看到,實現了想要的效果:this
(附:)完整的自定義表單驗證器:spa
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 } }; }; } }