Angular 2 - 反应式表单验证消息
Angular 2 - Reactive form Validation messages
我的 objective 是将我所有的验证消息放在组件而不是 html 文件中
我有一个注册页面,下面是字段:
public buildRegisterForm() {
this.userForm = this.fb.group({
firstName: ['', [Validators.required, Validators.minLength(3)]],
lastName: ['', [Validators.required, Validators.maxLength(50)]],
emailGroup: this.fb.group({
email: ['', [Validators.required, Validators.pattern(this.emailPattern)]],
retypeEmail: ['', Validators.required],
}, { validator: formMatcherValidator('email', 'retypeEmail') }),
passwordGroup: this.fb.group({
password: ['', [Validators.required, strongPasswordValidator()]],
retypePassword: ['', Validators.required],
}, { validator: formMatcherValidator('password', 'retypePassword')}),
});
}
我正在按照本教程 link 来实现我想要的
将我所有的验证消息放在组件文件中而不是 html 文件中。
export const validationMessages = {
'firstName': {
'required': 'Your first name is required.',
'minlength': 'Your first name must be at least 3 characters long.'
},
'lastName': {
'required': 'Your last name is required.',
'minlength': 'Your last name must be less than 50 characters long.'
},
'emailGroup': {
'email': {
'required': 'Your email is required',
'pattern': 'Your login email does not seem to be a valid email address.'
},
'retypeEmail': {
'required': 'Your retype email is required',
'match': 'The email provided do not match.'
},
},
'passwordGroup':{
'password': {
'required': 'Your password is required',
'strongPassword': 'Your password must be between 8 - 15 characters and must contain at least three of the following: upper case letter, lower case letter, number, symbol.'
},
'retypePassword': {
'required': 'Your retype password is required',
'match': 'The password provided do not match.'
}
}
onValueChanged 方法
private onValueChanged(data?: any) {
if (!this.userForm) { return; }
const form = this.userForm;
// tslint:disable-next-line:forin
for (const field in this.formErrors) {
// clear previous error message (if any)
this.formErrors[field] = '';
let control = form.get(field);
// console.log("control", control.dirty);
console.log("controlEmail", control);
if (control && (control.dirty || control.touched) && control.invalid) {
let messages = validationMessages[field];
// tslint:disable-next-line:forin
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
当我有多个 formBuider 组或嵌套对象时,此方法不起作用。这 1 有什么提示吗?
类似于此 How to validate reactive forms with nested form groups?
在我看来,您需要在 onValueChanged(data)
方法中创建一个嵌套循环。由于您有很多嵌套组,因此我不会复制它。但是嵌套循环是通用的,因此它适用于您的所有组。但这是一个只有一个嵌套组而不是多个嵌套组的示例。我正在使用英雄示例。
嵌套的组名为group
,里面的formcontrol叫做child
.
因此,代码中使用的 formErrors
应该在 group
:
中包含 child
formErrors = {
'name': '',
'power': '',
'group':{
'child': ''
}
};
所以大家一定要记得在模板中添加验证时,需要使用:
<div *ngIf="formErrors.group.child">
{{ formErrors.group.child }}
</div>
验证消息不会在 group
中,但就像其他验证消息一样:
validationMessages = {
'name': {
'required': 'Name is required.',
},
'power': {
'required': 'Power is required.'
},
'child': {
'required': 'Child is required.',
}
};
最后,修改后的onValueChanges
:
onValueChanged(data?: any) {
if (!this.heroForm) { return; }
const form = this.heroForm;
// iterate toplevel of formErrors
for (const field in this.formErrors) {
// check if the field corresponds a formgroup (controls is present)
if(form.get(field).controls ) {
// if yes, iterate the inner formfields
for(const subfield in form.get(field).controls) {
// in this example corresponds = "child", reset the error messages
this.formErrors[field][subfield] = '';
// now access the actual formfield
const control = form.get(field).controls[subfield];
// validate and show appropriate error message
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[subfield];
for (const key in control.errors) {
this.formErrors[field][subfield] += messages[key] + ' ';
}
}
}
}
// does not contain a nested formgroup, so just iterate like before making changes to this method
else {
const control = form.get(field);
this.formErrors[field] = '';
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
}
终于来个DEMO :)
Plunker
你必须记住,虽然在你的情况下这是可行的,但如果嵌套组中有嵌套组,这将不起作用,那么你必须在 onValueChanges
,但你这里没有这个问题 ;)
您还可以将以下内容与原始 onValueChanged
方法一起使用:
formErrors = {
'name': '',
'power': '',
'group.child':''
};
validationMessages = {
'name': {
'required': 'Name is required.',
},
'power': {
'required': 'Power is required.'
},
'group.child': {
'required': 'Child is required.',
}
};
onValueChanged(data?: any) {
if (!this.heroForm) { return; }
const form = this.heroForm;
for (const field in this.formErrors) {
// clear previous error message (if any)
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
我希望 Nehal 的解决方案有效,但我做不到。在使用 @AJT_82 代码后想出了我的解决方案。我的需求真的来自于想作为一个整体检查密码,而他的解决方案没有涵盖这一点。因此,我已经包括了我用来创建对我有用的完整解决方案的其他部分。
在发现我在 2 中使用的方法不再有效后,我在 angular 4 中尝试验证密码确认时遇到了这个问题。 angular.io 上的信息对此并没有多大帮助,因为很多信息分散在他们文档的不同区域。
所以,澄清一下,这遵循 Angular 的反应形式方法。我写这个是为了在我还有其他验证限制的情况下验证密码(密码必须在 4 到 24 个字符之间,是必需的,等等)。通过一些小的调整,此方法应该同样适用于电子邮件确认。
首先,为了比较一个组的验证器,html 表单必须有一个使用 formGroupName=" " 标识符标识的子组。这是在主要 [formGroup] 标识符内。比较输入必须在此 formGroupName 标记元素内。在我的例子中,它只是一个 div。
<div class="title">Hero Registration Form</div>
<form [formGroup]="regForm" id="regForm" (ngSubmit)="onSubmit()">
<div class="input">
<label for="heroname">Heroname</label> <input type="text"
id="heroname" class="form-control" formControlName="heroname"
required />
<div *ngIf="formErrors.heroname" class="hero-reg-alert">{{
formErrors.heroname }}</div>
</div>
<div class="input">
<label for="email">Email</label> <input type="email" id="email"
class="form-control" formControlName="email" required />
<div *ngIf="formErrors.email" class="hero-reg-alert">{{
formErrors.email }}</div>
</div>
<div formGroupName="password">
<div class="input">
<label for="password1">Password</label> <input type="password"
id="password1" class="form-control" formControlName="password1"
required />
<div *ngIf="formErrors.password.password1" class="hero-reg-alert">
{{ formErrors.password.password1 }}</div>
</div>
<div class="input">
<label for="password2">Re-Enter Password</label> <input
type="password" id="password2" class="form-control"
formControlName="password2" required />
<div
*ngIf="formErrors.password.password2 || formErrors.password.password"
class="hero-reg-alert">{{ formErrors.password.password }} {{
formErrors.password.password2 }}</div>
</div>
</div>
<button type="submit" [disabled]="!regForm.valid">
<span id="right-btntxt">SUBMIT</span>
</button>
</form>
您可能会注意到我将 formErrors 的子值设置为
formErrors.password.password
formErrors.password.password1
formErrors.password.password2
这些是在我的代码中构建的...
formErrors = {
'username': '',
'email': '',
'password': {
'password': '',
'password1': '',
'password2': ''
}
};
我是这样构建的,因为 'password1' 和 'password2' 为每个字段保留我的 formErrors,而 'password' 在不匹配的情况下保留我的错误(其中两个输入的密码不相等)。
这里是 onValueChanged() 公式。它检查一个字段是否是 FormGroup 的实例。如果是这样,它首先检查该字段组的自定义验证(将它们存储在 'this.formErrors[field][field]' 中),然后继续处理子字段验证。如果它不是 FieldGroup 的实例,则根据 angular.io 的指导文档中的示例处理验证。
onValueChanged(data?: any) {
if (!this.regForm) {return;}
const form = this.regForm;
for (const field in this.formErrors) {
const formControl = form.get(field);
if (formControl instanceof FormGroup) {
this.formErrors[field][field] = '';
// check for custom validation on field group
const control = form.get(field);
// handle validation for field group
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field][field] += messages[key] + ' ';
}
}
// handle validation for subfields
for (const subfield in formControl.controls) {
console.log('SUBFIELD', subfield);
this.formErrors[field][subfield] = '';
const control = formControl.controls[subfield];
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[subfield];
for (const key in control.errors) {
this.formErrors[field][subfield] += messages[key] + ' ';
}
}
}
}
// alternate validation handling for fields without subfields (AKA not in a group)
else {
const control = form.get(field);
this.formErrors[field] = '';
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
}
通过为 FieldGroup 提供一个具有自己名称的子字段,我们可以将验证存储在 FieldGroup 上。尝试使用常规 onValueChange 代码执行此操作会覆盖行中的子字段...
this.formErrors[field] = '';
不提供存储 FieldGroup 验证的位置会覆盖子字段或不处理 FieldGroup。
如果您需要它,这就是使用 buildFomr() 构建表单的方式;
buildForm(): void {
this.regForm = this.formBuilder.group({
'username': [this.registerUser.username, [
Validators.required,
Validators.minLength(4),
Validators.maxLength(24)
]
],
'email': [this.registerUser.email, [
Validators.email,
Validators.required
]
],
'password': this.formBuilder.group({
'password1': [this.registerUser.password, [
Validators.required,
Validators.minLength(8),
Validators.maxLength(24)
]
],
'password2': ['', [
Validators.required
]
]
}, {validator: this.passwordMatchValidator})
}); // end buildForm
this.regForm.valueChanges
.subscribe(data => this.onValueChanged(data));
this.onValueChanged(); // (re)set validation messages now
}
这是自定义验证函数...
passwordMatchValidator(control: FormGroup): {[key: string]: any} {
return control.get('password1').value !== control.get('password2').value ? {mismatch: true} : null;
}
public morningForm:FormGroup;
constructor(public fb: FormBuilder) { }
ngOnInit() {
this.morningForm = this.fb.group({
'country': [''],
'city' : [''],
'phone': ['']
});
this.setValidators();
}
private setValidators() {
let me = this;
let status = false;
me.morningForm.valueChanges.subscribe(fieldObj => {
for (const fieldName in fieldObj) {
// IF ENTER OR FIND THE VALUE THEN ALL FIELD SET AUTO REQUIRED VALIDATION.
if (fieldObj[fieldName] != '') {
status = true;
break;
}
}
});
this.setRequiredField(status)
}
private setRequiredField(status:any) {
let me = this;
let country = me.morningForm.get('country');
let city = me.morningForm.get('city');
let phone = me.morningForm.get('phone');
country.setValidators(null);
city.setValidators(null);
phone.setValidators(null);
if (status) {
country.setValidators([Validators.required]);
city.setValidators([Validators.required]);
phone.setValidators([Validators.required]);
}
country.updateValueAndValidity();
city.updateValueAndValidity();
phone.updateValueAndValidity();
}
我的 objective 是将我所有的验证消息放在组件而不是 html 文件中
我有一个注册页面,下面是字段:
public buildRegisterForm() {
this.userForm = this.fb.group({
firstName: ['', [Validators.required, Validators.minLength(3)]],
lastName: ['', [Validators.required, Validators.maxLength(50)]],
emailGroup: this.fb.group({
email: ['', [Validators.required, Validators.pattern(this.emailPattern)]],
retypeEmail: ['', Validators.required],
}, { validator: formMatcherValidator('email', 'retypeEmail') }),
passwordGroup: this.fb.group({
password: ['', [Validators.required, strongPasswordValidator()]],
retypePassword: ['', Validators.required],
}, { validator: formMatcherValidator('password', 'retypePassword')}),
});
}
我正在按照本教程 link 来实现我想要的
将我所有的验证消息放在组件文件中而不是 html 文件中。
export const validationMessages = {
'firstName': {
'required': 'Your first name is required.',
'minlength': 'Your first name must be at least 3 characters long.'
},
'lastName': {
'required': 'Your last name is required.',
'minlength': 'Your last name must be less than 50 characters long.'
},
'emailGroup': {
'email': {
'required': 'Your email is required',
'pattern': 'Your login email does not seem to be a valid email address.'
},
'retypeEmail': {
'required': 'Your retype email is required',
'match': 'The email provided do not match.'
},
},
'passwordGroup':{
'password': {
'required': 'Your password is required',
'strongPassword': 'Your password must be between 8 - 15 characters and must contain at least three of the following: upper case letter, lower case letter, number, symbol.'
},
'retypePassword': {
'required': 'Your retype password is required',
'match': 'The password provided do not match.'
}
}
onValueChanged 方法
private onValueChanged(data?: any) {
if (!this.userForm) { return; }
const form = this.userForm;
// tslint:disable-next-line:forin
for (const field in this.formErrors) {
// clear previous error message (if any)
this.formErrors[field] = '';
let control = form.get(field);
// console.log("control", control.dirty);
console.log("controlEmail", control);
if (control && (control.dirty || control.touched) && control.invalid) {
let messages = validationMessages[field];
// tslint:disable-next-line:forin
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
当我有多个 formBuider 组或嵌套对象时,此方法不起作用。这 1 有什么提示吗?
类似于此 How to validate reactive forms with nested form groups?
在我看来,您需要在 onValueChanged(data)
方法中创建一个嵌套循环。由于您有很多嵌套组,因此我不会复制它。但是嵌套循环是通用的,因此它适用于您的所有组。但这是一个只有一个嵌套组而不是多个嵌套组的示例。我正在使用英雄示例。
嵌套的组名为group
,里面的formcontrol叫做child
.
formErrors
应该在 group
:
child
formErrors = {
'name': '',
'power': '',
'group':{
'child': ''
}
};
所以大家一定要记得在模板中添加验证时,需要使用:
<div *ngIf="formErrors.group.child">
{{ formErrors.group.child }}
</div>
验证消息不会在 group
中,但就像其他验证消息一样:
validationMessages = {
'name': {
'required': 'Name is required.',
},
'power': {
'required': 'Power is required.'
},
'child': {
'required': 'Child is required.',
}
};
最后,修改后的onValueChanges
:
onValueChanged(data?: any) {
if (!this.heroForm) { return; }
const form = this.heroForm;
// iterate toplevel of formErrors
for (const field in this.formErrors) {
// check if the field corresponds a formgroup (controls is present)
if(form.get(field).controls ) {
// if yes, iterate the inner formfields
for(const subfield in form.get(field).controls) {
// in this example corresponds = "child", reset the error messages
this.formErrors[field][subfield] = '';
// now access the actual formfield
const control = form.get(field).controls[subfield];
// validate and show appropriate error message
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[subfield];
for (const key in control.errors) {
this.formErrors[field][subfield] += messages[key] + ' ';
}
}
}
}
// does not contain a nested formgroup, so just iterate like before making changes to this method
else {
const control = form.get(field);
this.formErrors[field] = '';
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
}
终于来个DEMO :)
Plunker
你必须记住,虽然在你的情况下这是可行的,但如果嵌套组中有嵌套组,这将不起作用,那么你必须在 onValueChanges
,但你这里没有这个问题 ;)
您还可以将以下内容与原始 onValueChanged
方法一起使用:
formErrors = {
'name': '',
'power': '',
'group.child':''
};
validationMessages = {
'name': {
'required': 'Name is required.',
},
'power': {
'required': 'Power is required.'
},
'group.child': {
'required': 'Child is required.',
}
};
onValueChanged(data?: any) {
if (!this.heroForm) { return; }
const form = this.heroForm;
for (const field in this.formErrors) {
// clear previous error message (if any)
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
我希望 Nehal 的解决方案有效,但我做不到。在使用 @AJT_82 代码后想出了我的解决方案。我的需求真的来自于想作为一个整体检查密码,而他的解决方案没有涵盖这一点。因此,我已经包括了我用来创建对我有用的完整解决方案的其他部分。
在发现我在 2 中使用的方法不再有效后,我在 angular 4 中尝试验证密码确认时遇到了这个问题。 angular.io 上的信息对此并没有多大帮助,因为很多信息分散在他们文档的不同区域。
所以,澄清一下,这遵循 Angular 的反应形式方法。我写这个是为了在我还有其他验证限制的情况下验证密码(密码必须在 4 到 24 个字符之间,是必需的,等等)。通过一些小的调整,此方法应该同样适用于电子邮件确认。
首先,为了比较一个组的验证器,html 表单必须有一个使用 formGroupName=" " 标识符标识的子组。这是在主要 [formGroup] 标识符内。比较输入必须在此 formGroupName 标记元素内。在我的例子中,它只是一个 div。
<div class="title">Hero Registration Form</div>
<form [formGroup]="regForm" id="regForm" (ngSubmit)="onSubmit()">
<div class="input">
<label for="heroname">Heroname</label> <input type="text"
id="heroname" class="form-control" formControlName="heroname"
required />
<div *ngIf="formErrors.heroname" class="hero-reg-alert">{{
formErrors.heroname }}</div>
</div>
<div class="input">
<label for="email">Email</label> <input type="email" id="email"
class="form-control" formControlName="email" required />
<div *ngIf="formErrors.email" class="hero-reg-alert">{{
formErrors.email }}</div>
</div>
<div formGroupName="password">
<div class="input">
<label for="password1">Password</label> <input type="password"
id="password1" class="form-control" formControlName="password1"
required />
<div *ngIf="formErrors.password.password1" class="hero-reg-alert">
{{ formErrors.password.password1 }}</div>
</div>
<div class="input">
<label for="password2">Re-Enter Password</label> <input
type="password" id="password2" class="form-control"
formControlName="password2" required />
<div
*ngIf="formErrors.password.password2 || formErrors.password.password"
class="hero-reg-alert">{{ formErrors.password.password }} {{
formErrors.password.password2 }}</div>
</div>
</div>
<button type="submit" [disabled]="!regForm.valid">
<span id="right-btntxt">SUBMIT</span>
</button>
</form>
您可能会注意到我将 formErrors 的子值设置为
formErrors.password.password
formErrors.password.password1
formErrors.password.password2
这些是在我的代码中构建的...
formErrors = {
'username': '',
'email': '',
'password': {
'password': '',
'password1': '',
'password2': ''
}
};
我是这样构建的,因为 'password1' 和 'password2' 为每个字段保留我的 formErrors,而 'password' 在不匹配的情况下保留我的错误(其中两个输入的密码不相等)。
这里是 onValueChanged() 公式。它检查一个字段是否是 FormGroup 的实例。如果是这样,它首先检查该字段组的自定义验证(将它们存储在 'this.formErrors[field][field]' 中),然后继续处理子字段验证。如果它不是 FieldGroup 的实例,则根据 angular.io 的指导文档中的示例处理验证。
onValueChanged(data?: any) {
if (!this.regForm) {return;}
const form = this.regForm;
for (const field in this.formErrors) {
const formControl = form.get(field);
if (formControl instanceof FormGroup) {
this.formErrors[field][field] = '';
// check for custom validation on field group
const control = form.get(field);
// handle validation for field group
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field][field] += messages[key] + ' ';
}
}
// handle validation for subfields
for (const subfield in formControl.controls) {
console.log('SUBFIELD', subfield);
this.formErrors[field][subfield] = '';
const control = formControl.controls[subfield];
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[subfield];
for (const key in control.errors) {
this.formErrors[field][subfield] += messages[key] + ' ';
}
}
}
}
// alternate validation handling for fields without subfields (AKA not in a group)
else {
const control = form.get(field);
this.formErrors[field] = '';
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
}
通过为 FieldGroup 提供一个具有自己名称的子字段,我们可以将验证存储在 FieldGroup 上。尝试使用常规 onValueChange 代码执行此操作会覆盖行中的子字段...
this.formErrors[field] = '';
不提供存储 FieldGroup 验证的位置会覆盖子字段或不处理 FieldGroup。
如果您需要它,这就是使用 buildFomr() 构建表单的方式;
buildForm(): void {
this.regForm = this.formBuilder.group({
'username': [this.registerUser.username, [
Validators.required,
Validators.minLength(4),
Validators.maxLength(24)
]
],
'email': [this.registerUser.email, [
Validators.email,
Validators.required
]
],
'password': this.formBuilder.group({
'password1': [this.registerUser.password, [
Validators.required,
Validators.minLength(8),
Validators.maxLength(24)
]
],
'password2': ['', [
Validators.required
]
]
}, {validator: this.passwordMatchValidator})
}); // end buildForm
this.regForm.valueChanges
.subscribe(data => this.onValueChanged(data));
this.onValueChanged(); // (re)set validation messages now
}
这是自定义验证函数...
passwordMatchValidator(control: FormGroup): {[key: string]: any} {
return control.get('password1').value !== control.get('password2').value ? {mismatch: true} : null;
}
public morningForm:FormGroup;
constructor(public fb: FormBuilder) { }
ngOnInit() {
this.morningForm = this.fb.group({
'country': [''],
'city' : [''],
'phone': ['']
});
this.setValidators();
}
private setValidators() {
let me = this;
let status = false;
me.morningForm.valueChanges.subscribe(fieldObj => {
for (const fieldName in fieldObj) {
// IF ENTER OR FIND THE VALUE THEN ALL FIELD SET AUTO REQUIRED VALIDATION.
if (fieldObj[fieldName] != '') {
status = true;
break;
}
}
});
this.setRequiredField(status)
}
private setRequiredField(status:any) {
let me = this;
let country = me.morningForm.get('country');
let city = me.morningForm.get('city');
let phone = me.morningForm.get('phone');
country.setValidators(null);
city.setValidators(null);
phone.setValidators(null);
if (status) {
country.setValidators([Validators.required]);
city.setValidators([Validators.required]);
phone.setValidators([Validators.required]);
}
country.updateValueAndValidity();
city.updateValueAndValidity();
phone.updateValueAndValidity();
}