Angular2 validator which relies on multiple form fields

Is it possible to create a validator which can use multiple values to decide if my field is valid?

e.g. if the customer's preferred contact method is by email then the email field should be required.

Thanks.


Updated with example code...


    import {Component, View} from 'angular2/angular2';
import {FormBuilder, Validators, formDirectives, ControlGroup} from 'angular2/forms';


@Component({
selector: 'customer-basic',
viewInjector: [FormBuilder]
})
@View({
templateUrl: 'app/components/customerBasic/customerBasic.html',
directives: [formDirectives]
})
export class CustomerBasic {
customerForm: ControlGroup;


constructor(builder: FormBuilder) {
this.customerForm = builder.group({
firstname: [''],
lastname: [''],
validateZip: ['yes'],
zipcode: ['', this.zipCodeValidator]
// I only want to validate using the function below if the validateZip control is set to 'yes'
});
}


zipCodeValidator(control) {
if (!control.value.match(/\d\d\d\d\d(-\d\d\d\d)?/)) {
return { invalidZipCode: true };
}
}


}
101489 次浏览

我认为目前最好的办法是创建一个表单组来保存控件。当您实例化 Control 时,在函数中传递它以验证它。例如:

    this.password = new Control('', Validators.required);
let x = this.password;
this.confirm = new Control('', function(c: Control){
if(typeof c.value === 'undefined' || c.value == "") return {required: "password required"};
if(c.value !== x.value)
return {error: "password mismatch"};
return null;
});

我知道这很大程度上取决于你运行的 angularjs2的版本

如果有人有更好的建议,比如编写一个自定义验证器(这可能是最好的方法) ,欢迎。

剪辑

您还可以使用 ControlGroup 并完全验证该组。

this.formGroup = new ControlGroup({}, function(c: ControlGroup){
var pass: Control = <Control>c.controls["password"];
var conf: Control = <Control>c.controls["confirm"];
pass.setErrors(null, true);
if(pass.value != null && pass.value != ""){
if(conf.value != pass.value){
pass.setErrors({error: "invalid"}, true);
return {error: "error"};
}
}
return null;
});

只需根据您的域编辑邮件。

在角度源上做了很多研究,但我找到了一个更好的方法。

constructor(...) {
this.formGroup = builder.group({
first_name:        ['', Validators.required],
matching_password: builder.group({
password: ['', Validators.required],
confirm:  ['', Validators.required]
}, this.matchPassword)
});


// expose easy access to passworGroup to html
this.passwordGroup = this.formGroup.controls.matching_password;
}


matchPassword(group): any {
let password = group.controls.password;
let confirm = group.controls.confirm;


// Don't kick in until user touches both fields
if (password.pristine || confirm.pristine) {
return null;
}


// Mark group as touched so we can add invalid class easily
group.markAsTouched();


if (password.value === confirm.value) {
return null;
}


return {
isValid: false
};
}

密码组的 HTML 部分

<div ng-control-group="matching_password" [class.invalid]="passwordGroup.touched && !passwordGroup.valid">
<div *ng-if="passwordGroup.touched && !passwordGroup.valid">Passwords must match.</div>
<div class="form-field">
<label>Password</label>
<input type="password" ng-control="password" placeholder="Your password" />
</div>
<div class="form-field">
<label>Password Confirmation</label>
<input type="password" ng-control="confirm" placeholder="Password Confirmation" />
</div>
</div>

详细说明 Matthew Wdaniel 的答案,因为它并不完全正确。下面是一些示例代码,它们显示了如何正确地将验证器分配给 ControlGroup

import {Component} from angular2/core
import {FormBuilder, Control, ControlGroup, Validators} from 'angular2/common'


@Component({
selector: 'my-app',
template: `
<form [ngFormModel]="form">
<label for="name">Name:</label>
<input id="name" type="text" ngControl="name">
<br>
<label for="email">Email:</label>
<input id="email" type="email" ngControl="email">
<br>
<div ngControlGroup="matchingPassword">
<label for="password">Password:</label>
<input id="password" type="password" ngControl="password">
<br>
<label for="confirmPassword">Confirm Password:</label>
<input id="confirmPassword" type="password" ngControl="confirmPassword">
</div>
</form>
<p>Valid?: \{\{form.valid}}</p>
<pre>\{\{form.value | json}}</pre>
`
})
export class App {
form: ControlGroup
constructor(fb: FormBuilder) {
this.form = fb.group({
name: ['', Validators.required],
email: ['', Validators.required]
matchingPassword: fb.group({
password: ['', Validators.required],
confirmPassword: ['', Validators.required]
}, {validator: this.areEqual})
});
}


areEqual(group: ControlGroup) {
let val;
let valid = true;


for (name in group.controls) {
if (val === undefined) {
val = group.controls[name].value
} else {
if (val !== group.controls[name].value) {
valid = false;
break;
}
}
}


if (valid) {
return null;
}


return {
areEqual: true
};
}
}

这里有一个工作示例: http://plnkr.co/edit/Zcbg2T3tOxYmhxs7vaAm?p=preview

为了重申其他发布的方法,这是我创建不涉及多个组的 FormGroup验证器的方法。

对于这个示例,只需提供 passwordconfirmPassword字段的键名。

// Example use of FormBuilder, FormGroups, and FormControls
this.registrationForm = fb.group({
dob: ['', Validators.required],
email: ['', Validators.compose([Validators.required,  emailValidator])],
password: ['', Validators.required],
confirmPassword: ['', Validators.required],
firstName: ['', Validators.required],
lastName: ['', Validators.required]
}, {validator: matchingPasswords('password', 'confirmPassword')})

为了让 Validators获取参数,它们需要返回一个 function,其中包含一个 FormGroup或者 FormControl作为参数。在本例中,我将验证 FormGroup

function matchingPasswords(passwordKey: string, confirmPasswordKey: string) {
return (group: FormGroup): {[key: string]: any} => {
let password = group.controls[passwordKey];
let confirmPassword = group.controls[confirmPasswordKey];


if (password.value !== confirmPassword.value) {
return {
mismatchedPasswords: true
};
}
}
}

从技术上讲,如果我知道它们的键,我可以验证任何两个值,但是我更喜欢将我的 Validators命名为与它们将返回的错误相同的名称。可以修改该函数以接受第三个参数,该参数表示返回的错误的键名。

更新于2016年12月6日 (v2.2.4)

完整例子: https://embed.plnkr.co/ukwCXm/

这里是另一个选项,我能够提出,这是不依赖于一个完整的或子 ControlGroup,但是是直接绑定到每个 Control

The problem I had was the controls that were dependent on each other weren't hierarchically together so I was unable to create a ControlGroup. Also, my CSS was setup that each control would leverage the existing angular classes to determine whether to display error styling, which was more complicated when dealing with a group validation instead of a control specific validation. Trying to determine if a single control was valid was not possible since the validation was tied to the group of controls and not each individual control.

在我的例子中,我需要一个选择框的值来确定是否需要另一个字段。

这是使用组件上的窗体生成器生成的。对于 select 模型,我没有直接将它绑定到请求对象的值,而是将它绑定到 get/set 函数,这些函数允许我处理控件的“ on change”事件。然后,我将能够根据选择控件的新值手动设置另一个控件的验证。

下面是相关的视图部分:

<select [ngFormControl]="form.controls.employee" [(ngModel)]="employeeModel">
<option value="" selected></option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
...
<input [ngFormControl]="form.controls.employeeID" type="text" maxlength="255" [(ngModel)]="request.empID" />

相关组成部分:

export class RequestComponent {
form: ControlGroup;
request: RequestItem;


constructor(private fb: FormBuilder) {
this.form = fb.group({
employee: new Control("", Validators.required),
empID: new Control("", Validators.compose([Validators.pattern("[0-9]{7}"]))
});


get employeeModel() {
return this.request.isEmployee;
}


set employeeModel(value) {
this.request.isEmployee = value;
if (value === "Yes") {
this.form.controls["empID"].validator = Validators.compose([Validators.pattern("[0-9]{7}"), Validators.required]);
this.form.controls["empID"].updateValueAndValidity();
}
else {
this.form.controls["empID"].validator = Validators.compose([Validators.pattern("[0-9]{7}")]);
this.form.controls["empID"].updateValueAndValidity();
}
}
}

在我的例子中,我总是将模式验证绑定到控件,所以 validator总是被设置为某个值,但是我认为如果没有任何验证绑定到控件,那么可以将 validator设置为 null。

更新: 还有其他捕获模型更改的方法,如 (ngModelChange)=changeFunctionName($event)或通过使用 this.form.controls["employee"].valueChanges.subscribe(data => ...))订阅控制值更改

我正在使用角度2RC.5,但无法找到控制组,基于有益的答案从戴夫。我发现 FormGroup 可以工作。所以我对 Dave 的代码做了一些小小的更新,我想我应该和其他人分享一下。

在组件文件中,为 FormGroup 添加一个导入:

import {FormGroup} from "@angular/forms";

在需要直接访问表单控件时,定义输入:

oldPassword = new FormControl("", Validators.required);
newPassword = new FormControl("", Validators.required);
newPasswordAgain = new FormControl("", Validators.required);

在构造函数中,实例化窗体:

this.form = fb.group({
"oldPassword": this.oldPassword,
"newPassword": this.newPassword,
"newPasswordAgain": this.newPasswordAgain
}, {validator: this.matchingPasswords('newPassword', 'newPasswordAgain')});

在类中添加 matchingPasswords函数:

matchingPasswords(passwordKey: string, passwordConfirmationKey: string) {
return (group: FormGroup) => {
let passwordInput = group.controls[passwordKey];
let passwordConfirmationInput = group.controls[passwordConfirmationKey];


if (passwordInput.value !== passwordConfirmationInput.value) {
return passwordConfirmationInput.setErrors({notEquivalent: true})
}
}
}

希望这对那些使用 RC.5的人有所帮助。注意,我还没有在 RC.6上测试过。

正在寻找这一点,并最终使用来自 ng2验证包(https://www.npmjs.com/package/ng2-validation)的 equalTo

这里有一个例子: 模板驱动:

<input type="password" ngModel name="password" #password="ngModel" required/>
<p *ngIf="password.errors?.required">required error</p>
<input type="password" ngModel name="certainPassword" #certainPassword="ngModel" [equalTo]="password"/>
<p *ngIf="certainPassword.errors?.equalTo">equalTo error</p>

模型驱动:

let password = new FormControl('', Validators.required);
let certainPassword = new FormControl('', CustomValidators.equalTo(password));


this.form = new FormGroup({
password: password,
certainPassword: certainPassword
});

Template:

<form [formGroup]="form">
<input type="password" formControlName="password"/>
<p *ngIf="form.controls.password.errors?.required">required error</p>
<input type="password" formControlName="certainPassword"/>
<p *ngIf="form.controls.certainPassword.errors?.equalTo">equalTo error</p>
</form>

下面是我用来确保一个字段中的年龄大于或等于另一个字段中的年龄的版本。我还使用了表单组,所以我使用的是 group.get函数而不是 group.controls[]函数

import { FormGroup } from '@angular/forms';


export function greaterThanOrEqualTo(sourceKey: string, targetKey: string) {
return (group: FormGroup) => {
let sourceInput = group.get(sourceKey);
let targetInput = group.get(targetKey);


console.log(sourceInput);
console.log(targetInput);


if (targetInput.value < sourceInput.value) {
return targetInput.setErrors({ notGreaterThanOrEqualTo: true })
}
}
}

And in the component:

    this.form = this._fb.group({


clientDetails: this._fb.group({
currentAge: ['', [Validators.required, Validators.pattern('^((1[89])|([2-9][0-9])|100)$')]],
expectedRetirementAge: ['', [Validators.required]]
}),


},
{
validator: greaterThanOrEqualTo('clientDetails.currentAge', 'clientDetails.expectedRetirementAge')
});

When implementing validators for multiple form fields, you will have to make sure, that validators are re-evaluated when each of the form control is updated. Most of the examples doesn't provide a solution for such scenario, but this is very important for data consistency and correct behavior.

请看我的角度2的自定义验证器的实现,它考虑到了这一点: https://gist.github.com/slavafomin/17ded0e723a7d3216fb3d8bf845c2f30

我正在使用 otherControl.valueChanges.subscribe()监听其他控件的更改,并使用 thisControl.updateValueAndValidity()在其他控件更改时触发另一轮验证。


我正在复制以下代码,以备将来参考:

匹配其他验证程序

import {FormControl} from '@angular/forms';




export function matchOtherValidator (otherControlName: string) {


let thisControl: FormControl;
let otherControl: FormControl;


return function matchOtherValidate (control: FormControl) {


if (!control.parent) {
return null;
}


// Initializing the validator.
if (!thisControl) {
thisControl = control;
otherControl = control.parent.get(otherControlName) as FormControl;
if (!otherControl) {
throw new Error('matchOtherValidator(): other control is not found in parent group');
}
otherControl.valueChanges.subscribe(() => {
thisControl.updateValueAndValidity();
});
}


if (!otherControl) {
return null;
}


if (otherControl.value !== thisControl.value) {
return {
matchOther: true
};
}


return null;


}


}

用法

以下是如何将其用于反应形式:

private constructForm () {
this.form = this.formBuilder.group({
email: ['', [
Validators.required,
Validators.email
]],
password: ['', Validators.required],
repeatPassword: ['', [
Validators.required,
matchOtherValidator('password')
]]
});
}

更多最新的验证器可以在这里找到: Moebius-mlm/ng-validators

角度4密码匹配验证规则。

如果您需要错误控制字段,那么您可以这样做。

createForm() {
this.ngForm = this.fb.group({
'first_name': ["", Validators.required ],
'last_name' : ["", Validators.compose([Validators.required, Validators.minLength(3)]) ],
'status' : ['active', Validators.compose([Validators.required])],
'phone':[null],
'gender':['male'],
'address':[''],
'email':['', Validators.compose([
Validators.required,
Validators.email])],
'password':['', Validators.compose([Validators.required])],
'confirm_password':['', Validators.compose([Validators.required])]
}, {validator: this.matchingPassword('password', 'confirm_password')});
}

Then your need to declaration this this method in constructor method 就像。

constructor(
private fb: FormBuilder


) {
this.createForm();
}

不要在 ControlGroup 上设置错误,而是在实际字段上设置如下:

    matchingPassword(passwordKey: string, confirmPasswordKey: string) {
return (group: FormGroup): {[key: string]: any} => {
let password = group.controls[passwordKey];
let confirm_password = group.controls[confirmPasswordKey];


if (password.value !== confirm_password.value) {
return {
mismatchedPasswords: true
};
}
}
}

密码组的 HTML 部分

<form [formGroup]="ngForm" (ngSubmit)="ngSubmit()">
<div class="form-group">
<label class="control-label" for="inputBasicPassword"> Password <span class="text-danger">*</span></label>
<input type="password" class="form-control" formControlName="password" placeholder="Password" name="password" required>
<div class="alert text-danger" *ngIf="!ngForm.controls['password'].valid && ngForm.controls['password'].touched">This Field is Required.</div>
</div>
\{\{ngForm.value.password | json}}
<div class="form-group">
<label class="control-label" for="inputBasicPassword">Confirm Password <span class="text-danger">*</span></label>
<input type="password" class="form-control" name="confirm_password" formControlName="confirm_password" placeholder="Confirm Password" match-password="password">


<div class='alert text-danger' *ngIf="ngForm.controls.confirm_password.touched && ngForm.hasError('mismatchedPasswords')">
Passwords doesn't match.
</div>
</div>
<button type="submit" [disabled]="!ngForm.valid" class="btn btn-primary ladda-button" data-plugin="ladda" data-style="expand-left" disabled="disabled"><span class="ladda-label">
<i class="fa fa-save"></i>  Create an account
<span class="ladda-spinner"></span><div class="ladda-progress" style="width: 0px;"></div>
</span><span class="ladda-spinner"></span></button>
</form>

I tried most of these answers but none of them worked for me. I found a working example here Https://scotch.io/@ibrahimalsurkhi/match-password-validation-with-angular-2

我建议使用库 ng-form-rules。它是一个令人敬畏的库,可以创建所有不同类型的表单,其验证逻辑与组件分离,并且可以依赖于表单中其他区域的值更改。他们有 great documentation 例子video that shows a bunch of its functionality。像这样进行验证,您试图做的事情是微不足道的。

你可以 查看他们的自述文件为一些高层次的信息和一个基本的例子。

角度8验证密码确认字段的示例

供参考: 如果在验证通过后主“ password”字段发生更改,则 没有将更新 passwordConsure 字段上的验证。 但是,当用户键入密码字段时,可以使密码确认字段无效

<input
type="password"
formControlName="password"
(input)="registerForm.get('passwordConfirm').setErrors({'passwordMatches': true})"
/>

Register.Component. ts

import { PasswordConfirmValidator } from './password-confirm-validator';
export class RegisterComponent implements OnInit {
registerForm: FormGroup = this.createRegisterForm({
username: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', [
Validators.required,
Validators.pattern('^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$'),
Validators.minLength(8)
]),
passwordConfirm: new FormControl('', [
Validators.required,
PasswordConfirmValidator //custom validator
])
});
}

密码-确认-验证程序

import { AbstractControl } from '@angular/forms';


export function PasswordConfirmValidator(control: AbstractControl) {
if(void 0 === control){ return null; }
if(
void 0 !== control.parent &&
void 0 !== control.parent.controls &&
void 0 !== control.parent.controls['password'] &&
control.parent.controls['password'].value === control.value
){
return null;
}
return {passwordMatches: true};
}

详见 register.Component. html

\{\{registerForm.get('passwordConfirm').hasError('passwordMatches')}}