根据条件动态 add/remove 个验证器
Dynamically add/remove validators based on condition
场景:
最初我有一个文本框(Name1
)、一个日期选择器(DOB1
) 和一个复选框(Compare
)。 Name1
和 DOB1
都是必需的。单击复选框时,将动态添加另外两个表单控件,命名为 Name2
和 DOB2
,并且需要 Name1
或 DOB2
中的任何一个。
所以有效的形式是
Name1
DOB1
Name2
或 //如果 Name2
有效则需要从 DOB2
中删除所需的验证器
Name1
DOB1
DOB2
或 //如果 DOB2
有效则需要从 Name2
中删除所需的验证器
Name1
DOB1
Name2
DOB2
在上述所有情况下,表单均有效显示启用提交按钮。
问题:
我曾尝试使用 setValidators,但仍然无法弄清楚我缺少什么。当我单击复选框时,只有当所有四个控件都有效时,表单才有效。我只需要其中任何三个有效。
代码:
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
<ion-card class="person1">
<ion-card-content>
<ion-list lines="full" class="ion-no-margin ion-no-padding">
<ion-item>
<ion-label position="stacked">Name / Number <ion-text color="danger">*</ion-text>
</ion-label>
<ion-input type="text" formControlName="NameNumber"></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">Date of birth<ion-text color="danger">*</ion-text>
</ion-label>
<ion-datetime required placeholder="Select Date" formControlName="DateOfBirth"></ion-datetime>
</ion-item>
</ion-list>
</ion-card-content>
</ion-card>
<ion-card class="person2" *ngIf="isComparisonChecked">
<ion-card-content>
<ion-list lines="full" class="ion-no-margin ion-no-padding">
<ion-item>
<ion-label position="stacked">Name / Number <ion-text color="danger">*</ion-text>
</ion-label>
<ion-input type="text" formControlName="NameNumber2"></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">Date of birth<ion-text color="danger">*</ion-text>
</ion-label>
<ion-datetime required placeholder="Select Date" formControlName="DateOfBirth2"></ion-datetime>
</ion-item>
</ion-list>
</ion-card-content>
</ion-card>
<ion-item class="compare-section" lines="none">
<ion-label>Compare</ion-label>
<ion-checkbox color="danger" formControlName="IsCompare"></ion-checkbox>
</ion-item>
<div class="ion-padding">
<ion-button color="danger" *ngIf="LicensedStatus" [disabled]="!this.profileForm.valid" expand="block"
type="submit" class="ion-no-margin">Submit</ion-button>
</div>
</form>
Ts:
profileForm = new FormGroup({
NameNumber: new FormControl('', [Validators.required, Validators.pattern('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$')]),
DateOfBirth: new FormControl('', Validators.required),
IsCompare: new FormControl(false)
});
...
this.profileForm.get('IsCompare').valueChanges.subscribe(checked => {
if (checked) {
this.profileForm.addControl('NameNumber2', new FormControl('', [Validators.required, Validators.pattern('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$')]));
this.profileForm.addControl('DateOfBirth2', new FormControl('', Validators.required));
this.profileForm.get('NameNumber2').valueChanges.subscribe(() => {
if (this.profileForm.get('NameNumber2').valid) {
this.profileForm.get('DateOfBirth2').clearValidators();
}
else {
this.profileForm.get('DateOfBirth2').setValidators([Validators.required]);
}
this.profileForm.get('DateOfBirth2').updateValueAndValidity();
});
this.profileForm.get('DateOfBirth2').valueChanges.subscribe(() => {
if (this.profileForm.get('DateOfBirth2').valid) {
this.profileForm.get('NameNumber2').clearValidators();
}
else {
this.profileForm.get('NameNumber2').setValidators([Validators.required, Validators.pattern('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$')]);
}
this.profileForm.get('NameNumber2').updateValueAndValidity();
});
}
else {
this.profileForm.removeControl('NameNumber2');
this.profileForm.removeControl('DateOfBirth2');
}
});
我在这里错过了什么?
更新#1:
我已经更新了上面的代码。如果我使用 updateValueAndValidity
我会在控制台中收到此错误
试试下面的代码。
this.profileForm.get('DateOfBirth2').setValidators([Validators.required]);
this.profileForm.get('DateOfBirth2').updateValueAndValidity();
使用 rxjs/operators 中的 distinctUntilChanged 将解决超出最大调用堆栈大小的错误。
从
更改行
this.profileForm.get('NameNumber2').valueChanges.subscribe(() => {
到
this.profileForm.get('NameNumber2').valueChanges.pipe(distinctUntilChanged()).subscribe(() => {
因此,整体代码将如下更改。
import { distinctUntilChanged } from 'rxjs/operators';
this.profileForm.get('NameNumber2').valueChanges.pipe(distinctUntilChanged()).subscribe(() => {
if (this.profileForm.get('NameNumber2').valid) {
this.profileForm.get('DateOfBirth2').clearValidators();
}
else {
this.profileForm.get('DateOfBirth2').setValidators([Validators.required]);
}
this.profileForm.get('DateOfBirth2').updateValueAndValidity();
});
this.profileForm.get('DateOfBirth2').valueChanges.pipe(distinctUntilChanged()).subscribe(() => {
if (this.profileForm.get('DateOfBirth2').valid) {
this.profileForm.get('NameNumber2').clearValidators();
}
else {
this.profileForm.get('NameNumber2').setValidators([Validators.required, Validators.pattern('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$')]);
}
this.profileForm.get('NameNumber2').updateValueAndValidity();
});
我 运行 上面更改的代码,表单有效,并且为您提到的所有场景启用了提交按钮。
为什么不对所有表单使用 customValidator?您发出不同的错误并检查表格中的错误。辅助功能指示您的字段有错误
有些喜欢:
form=new FormGroup({
name1:new FormControl(),
date1:new FormControl(),
compare:new FormControl(),
name2:new FormControl(),
date2:new FormControl(),
},this.customValidator())
hasError(error:string)
{
return this.form.errors?this.form.errors.error.find(x=>x==error):null
}
customValidator()
{
return (form:FormGroup)=>{
const errors=[];
if (!form.value.compare)
{
if (!form.value.name1)
errors.push('name1')
if (!form.value.date1)
errors.push('date1')
}
else
{
....
}
return errors.length?{error:errors}:null
}
}
你的表格像
<form [formGroup]="form">
<input formControlName="name1"/>
<span *ngIf="hasError('name1')">*</span>
<input formControlName="date1"/>
<span *ngIf="hasError('date1')">*</span>
<br/>
<input type="checkbox" formControlName="compare"/>
<br/>
<input *ngIf="form.get('compare').value" formControlName="name2"/>
<span *ngIf="hasError('name2')">*</span>
<input *ngIf="form.get('compare').value" formControlName="date2"/>
<span *ngIf="hasError('date2')">*</span>
</form>
另一个想法类似,有一个 customValidator return 始终为 null,但使用 setErrors 手动为您的字段提供错误
customValidator()
{
return (form:FormGroup)=>{
const errors=[];
if (!form.value.compare)
{
if (!form.value.name1)
errors.push('name1')
if (!form.value.date1)
errors.push('date1')
}
else
{
....other logic...
}
form.get('name1').setErrors(errors.find(x=>x=='name1')?{error:"required"}:null)
form.get('date1').setErrors(errors.find(x=>x=='date1')?{error:"required"}:null)
form.get('name2').setErrors(errors.find(x=>x=='name2')?{error:"required"}:null)
form.get('date2').setErrors(errors.find(x=>x=='date2')?{error:"required"}:null)
return null
}
}
发生这种情况是因为 updateValueAndValidity()
发出另一个 valueChanges
事件。因此,您的订阅会无限地相互触发。
this.profileForm.get('NameNumber2').valueChanges.subscribe(() => {
// omitted
this.profileForm.get('DateOfBirth2').updateValueAndValidity(); // Triggers valueChanges for 'DateOfBirth2'
});
this.profileForm.get('DateOfBirth2').valueChanges.subscribe(() => {
// omitted
this.profileForm.get('NameNumber2').updateValueAndValidity(); // Triggers valueChanges for 'NameNumber2'
});
以前的帖子中已经描述了一种避免这种情况的方法:使用 distinctUntilChanged
。
尽管方法本身内置了一种更简洁的方法:updateValueAndValidity()
使用一个对象来配置其行为。 updateValueAndValidity({emitEvent: false})
将阻止发出 valueChanges
事件,从而停止事件循环。
this.profileForm.get('NameNumber2').valueChanges.subscribe(() => {
// omitted
this.profileForm.get('DateOfBirth2').updateValueAndValidity({emitEvent:false}); // Does NOT trigger valueChanges
});
this.profileForm.get('DateOfBirth2').valueChanges.subscribe(() => {
// omitted
this.profileForm.get('NameNumber2').updateValueAndValidity({emitEvent:false}); // Does NOT trigger valueChanges
});
场景:
最初我有一个文本框(Name1
)、一个日期选择器(DOB1
) 和一个复选框(Compare
)。 Name1
和 DOB1
都是必需的。单击复选框时,将动态添加另外两个表单控件,命名为 Name2
和 DOB2
,并且需要 Name1
或 DOB2
中的任何一个。
所以有效的形式是
Name1
DOB1
Name2
或 //如果Name2
有效则需要从DOB2
中删除所需的验证器
Name1
DOB1
DOB2
或 //如果DOB2
有效则需要从Name2
中删除所需的验证器
Name1
DOB1
Name2
DOB2
在上述所有情况下,表单均有效显示启用提交按钮。
问题:
我曾尝试使用 setValidators,但仍然无法弄清楚我缺少什么。当我单击复选框时,只有当所有四个控件都有效时,表单才有效。我只需要其中任何三个有效。
代码:
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
<ion-card class="person1">
<ion-card-content>
<ion-list lines="full" class="ion-no-margin ion-no-padding">
<ion-item>
<ion-label position="stacked">Name / Number <ion-text color="danger">*</ion-text>
</ion-label>
<ion-input type="text" formControlName="NameNumber"></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">Date of birth<ion-text color="danger">*</ion-text>
</ion-label>
<ion-datetime required placeholder="Select Date" formControlName="DateOfBirth"></ion-datetime>
</ion-item>
</ion-list>
</ion-card-content>
</ion-card>
<ion-card class="person2" *ngIf="isComparisonChecked">
<ion-card-content>
<ion-list lines="full" class="ion-no-margin ion-no-padding">
<ion-item>
<ion-label position="stacked">Name / Number <ion-text color="danger">*</ion-text>
</ion-label>
<ion-input type="text" formControlName="NameNumber2"></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">Date of birth<ion-text color="danger">*</ion-text>
</ion-label>
<ion-datetime required placeholder="Select Date" formControlName="DateOfBirth2"></ion-datetime>
</ion-item>
</ion-list>
</ion-card-content>
</ion-card>
<ion-item class="compare-section" lines="none">
<ion-label>Compare</ion-label>
<ion-checkbox color="danger" formControlName="IsCompare"></ion-checkbox>
</ion-item>
<div class="ion-padding">
<ion-button color="danger" *ngIf="LicensedStatus" [disabled]="!this.profileForm.valid" expand="block"
type="submit" class="ion-no-margin">Submit</ion-button>
</div>
</form>
Ts:
profileForm = new FormGroup({
NameNumber: new FormControl('', [Validators.required, Validators.pattern('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$')]),
DateOfBirth: new FormControl('', Validators.required),
IsCompare: new FormControl(false)
});
...
this.profileForm.get('IsCompare').valueChanges.subscribe(checked => {
if (checked) {
this.profileForm.addControl('NameNumber2', new FormControl('', [Validators.required, Validators.pattern('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$')]));
this.profileForm.addControl('DateOfBirth2', new FormControl('', Validators.required));
this.profileForm.get('NameNumber2').valueChanges.subscribe(() => {
if (this.profileForm.get('NameNumber2').valid) {
this.profileForm.get('DateOfBirth2').clearValidators();
}
else {
this.profileForm.get('DateOfBirth2').setValidators([Validators.required]);
}
this.profileForm.get('DateOfBirth2').updateValueAndValidity();
});
this.profileForm.get('DateOfBirth2').valueChanges.subscribe(() => {
if (this.profileForm.get('DateOfBirth2').valid) {
this.profileForm.get('NameNumber2').clearValidators();
}
else {
this.profileForm.get('NameNumber2').setValidators([Validators.required, Validators.pattern('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$')]);
}
this.profileForm.get('NameNumber2').updateValueAndValidity();
});
}
else {
this.profileForm.removeControl('NameNumber2');
this.profileForm.removeControl('DateOfBirth2');
}
});
我在这里错过了什么?
更新#1:
我已经更新了上面的代码。如果我使用 updateValueAndValidity
我会在控制台中收到此错误
试试下面的代码。
this.profileForm.get('DateOfBirth2').setValidators([Validators.required]);
this.profileForm.get('DateOfBirth2').updateValueAndValidity();
使用 rxjs/operators 中的 distinctUntilChanged 将解决超出最大调用堆栈大小的错误。
从
更改行this.profileForm.get('NameNumber2').valueChanges.subscribe(() => {
到
this.profileForm.get('NameNumber2').valueChanges.pipe(distinctUntilChanged()).subscribe(() => {
因此,整体代码将如下更改。
import { distinctUntilChanged } from 'rxjs/operators';
this.profileForm.get('NameNumber2').valueChanges.pipe(distinctUntilChanged()).subscribe(() => {
if (this.profileForm.get('NameNumber2').valid) {
this.profileForm.get('DateOfBirth2').clearValidators();
}
else {
this.profileForm.get('DateOfBirth2').setValidators([Validators.required]);
}
this.profileForm.get('DateOfBirth2').updateValueAndValidity();
});
this.profileForm.get('DateOfBirth2').valueChanges.pipe(distinctUntilChanged()).subscribe(() => {
if (this.profileForm.get('DateOfBirth2').valid) {
this.profileForm.get('NameNumber2').clearValidators();
}
else {
this.profileForm.get('NameNumber2').setValidators([Validators.required, Validators.pattern('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$')]);
}
this.profileForm.get('NameNumber2').updateValueAndValidity();
});
我 运行 上面更改的代码,表单有效,并且为您提到的所有场景启用了提交按钮。
为什么不对所有表单使用 customValidator?您发出不同的错误并检查表格中的错误。辅助功能指示您的字段有错误 有些喜欢:
form=new FormGroup({
name1:new FormControl(),
date1:new FormControl(),
compare:new FormControl(),
name2:new FormControl(),
date2:new FormControl(),
},this.customValidator())
hasError(error:string)
{
return this.form.errors?this.form.errors.error.find(x=>x==error):null
}
customValidator()
{
return (form:FormGroup)=>{
const errors=[];
if (!form.value.compare)
{
if (!form.value.name1)
errors.push('name1')
if (!form.value.date1)
errors.push('date1')
}
else
{
....
}
return errors.length?{error:errors}:null
}
}
你的表格像
<form [formGroup]="form">
<input formControlName="name1"/>
<span *ngIf="hasError('name1')">*</span>
<input formControlName="date1"/>
<span *ngIf="hasError('date1')">*</span>
<br/>
<input type="checkbox" formControlName="compare"/>
<br/>
<input *ngIf="form.get('compare').value" formControlName="name2"/>
<span *ngIf="hasError('name2')">*</span>
<input *ngIf="form.get('compare').value" formControlName="date2"/>
<span *ngIf="hasError('date2')">*</span>
</form>
另一个想法类似,有一个 customValidator return 始终为 null,但使用 setErrors 手动为您的字段提供错误
customValidator()
{
return (form:FormGroup)=>{
const errors=[];
if (!form.value.compare)
{
if (!form.value.name1)
errors.push('name1')
if (!form.value.date1)
errors.push('date1')
}
else
{
....other logic...
}
form.get('name1').setErrors(errors.find(x=>x=='name1')?{error:"required"}:null)
form.get('date1').setErrors(errors.find(x=>x=='date1')?{error:"required"}:null)
form.get('name2').setErrors(errors.find(x=>x=='name2')?{error:"required"}:null)
form.get('date2').setErrors(errors.find(x=>x=='date2')?{error:"required"}:null)
return null
}
}
发生这种情况是因为 updateValueAndValidity()
发出另一个 valueChanges
事件。因此,您的订阅会无限地相互触发。
this.profileForm.get('NameNumber2').valueChanges.subscribe(() => {
// omitted
this.profileForm.get('DateOfBirth2').updateValueAndValidity(); // Triggers valueChanges for 'DateOfBirth2'
});
this.profileForm.get('DateOfBirth2').valueChanges.subscribe(() => {
// omitted
this.profileForm.get('NameNumber2').updateValueAndValidity(); // Triggers valueChanges for 'NameNumber2'
});
以前的帖子中已经描述了一种避免这种情况的方法:使用 distinctUntilChanged
。
尽管方法本身内置了一种更简洁的方法:updateValueAndValidity()
使用一个对象来配置其行为。 updateValueAndValidity({emitEvent: false})
将阻止发出 valueChanges
事件,从而停止事件循环。
this.profileForm.get('NameNumber2').valueChanges.subscribe(() => {
// omitted
this.profileForm.get('DateOfBirth2').updateValueAndValidity({emitEvent:false}); // Does NOT trigger valueChanges
});
this.profileForm.get('DateOfBirth2').valueChanges.subscribe(() => {
// omitted
this.profileForm.get('NameNumber2').updateValueAndValidity({emitEvent:false}); // Does NOT trigger valueChanges
});