在 Angular 验证器中链接两个字段
Linking two fields in an Angular validator
我有一个 Angular 9 表单,其中四个字段相关。一个是复选框,其余是输入。选中复选框时,输入不应为空,但未选中时,则无所谓。我想为此制作验证器,以便仅当字段为空且第一个字段设置为 true 时才会出现错误。
我还考虑过创建一个本地布尔值来表示复选标记的状态,然后像这样将其传递给验证器。
export function linkedFieldValidator(toggler: boolean): ValidatorFn {
console.log('updated');
return (control: AbstractControl): {[key: string]: any} | null => {
return (toggler && control.value === '') ? {linkedField: {value: control.value}} : null;
};
}
...
field: new FormControl('', linkedFieldValidator(this.checkboxvalue)),
...
这不起作用,但是,我想,因为它只传递布尔值一次,之后不会更新。即使调用 updateValueAndValidity()
也不起作用,这对我来说很奇怪(如果不是这样,那么它的目的是什么?)。
我的 FormGroup
的结构看起来像这样:
this.form = this.formBuilder.group({
name: new FormControl(''), // don't care
address: new FormControl(''), // don't care
car: new FormControl(false), // do care - this is the checkmark
license_plate: new FormControl('', Validators.pattern(MY_LICENSE_PLATE_REGEX)), // shouldn't be empty when car
mileage: new FormControl('') // shouldn't be empty when car
hair: new FormControl(false), // do care - this is the checkmark
hair_color: new FormControl(''), // shouldn't be empty when hair
});
如您所见,我有几个 FormControll
相互关联,我只希望将其中几个链接起来。另一个需要注意的重要事项是,虽然如果违反其中一个条件可能会使整个表单无效,但我希望能够单独解决每个错误,以便我可以在适当的位置显示适当的消息。
我没有更多的想法,有人可以帮助我吗?我正在使用反应形式。
编辑 1:
DEMO 对子表单(输入所在的位置)和主表单进行验证
原回答:
您可以使用cross-fields validation来组合检查多个字段。这里我有 1 个复选框(初始状态为 false
或未选中)和 3 个输入字段。在我的自定义验证器中,我只是检查值并设置相应的表单验证错误:
export class ProfileEditorComponent {
myForm = new FormGroup({
'checker': new FormControl(false),
'name': new FormControl(),
'middleName': new FormControl(),
'lastName': new FormControl()
}, { validators: myCustomValidator });
constructor(private fb: FormBuilder) { }
}
export const myCustomValidator : ValidatorFn = (control: FormGroup): ValidationErrors | null => {
const checker = control.get('checker').value;
const name = control.get('name');
if (checker) {
if (name.value===null || name.value==="") {
return {'firstNameMissing': true};
// TODO: do the same for the other fields or any field combination
} else {
return null;
}
}
return null;
};
然后错误信息的显示如下:
<label>
First Name:
<input type="text" formControlName="name">
</label>
<div *ngIf="myForm.errors?.firstNameMissing && (myForm.touched || myForm.dirty)" class="cross-validation-error-message alert alert-danger">
First name required.
</div>
问题是您只将初始值传递给 linkFieldValidator
函数。
为了动态获得值,您可以通过 FormGroup
传递 linkFieldValidator
,如下所示:
readonly formGroup = this.formBuilder.group(
{
checkbox: '',
name: ''
},
{ validator: linkedFieldValidator }
);
完整样本:
import { ChangeDetectionStrategy, Component } from '@angular/core';
import {
AbstractControl,
FormBuilder,
FormGroup,
ValidationErrors,
ValidatorFn,
} from '@angular/forms';
export const linkedFieldValidator = (formGroup: FormGroup): ValidationErrors | null => {
const [checkboxFormControlValue, nameFormControlValue] = [
formGroup.get('checkbox')!.value,
formGroup.get('name')!.value
];
return checkboxFormControlValue && !nameFormControlValue
? { linkedField: { value: nameFormControlValue } }
: null;
};
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'input-overview-example',
styleUrls: ['input-overview-example.css'],
templateUrl: 'input-overview-example.html'
})
export class InputOverviewExample {
readonly formGroup = this.formBuilder.group(
{
checkbox: '',
name: ''
},
{ validator: linkedFieldValidator }
);
constructor(private readonly formBuilder: FormBuilder) {}
}
编辑 1:
如果您需要将错误驻留在每个表单控件中,您可以将 linkedFieldValidator
更改为:
export const linkedFieldValidator = (formGroup: FormGroup): null => {
const { value: checkboxFormControlValue } = formGroup.get('checkbox')!;
const inputFormControls = [
formGroup.get('input1')!,
formGroup.get('input2')!,
formGroup.get('input3')!,
];
inputFormControls.forEach(inputFormControl => {
const { value } = inputFormControl;
const errors = checkboxFormControlValue && !value ? { linkedField: { value } } : null;
inputFormControl.setErrors(errors);
});
return null;
};
注意,如果需要保留其他错误,可能需要在setErrors
之前做一些处理。
编辑 2:
对于可以有多个链接字段的通用方法,您可以这样做:
type LinkedFormControl = Record<string, string | readonly string[]>;
const arrayify = <T>(itemOrItems: T | readonly T[]): readonly T[] => {
return Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
};
const getErrorObjectSanitized = <T extends object>(obj: T): T | null => {
return Object.keys(obj).length === 0 ? null : obj;
};
const getErrorsFor = (
checkerValue: boolean,
formControl: FormControl,
): object | null => {
const { errors, value } = formControl;
const { error, ...oldErrors } = errors || {};
const processedErrors = {
...(checkerValue && !value ? { error: true } : {}),
...oldErrors,
};
return getErrorObjectSanitized(processedErrors);
};
export const linkedFieldValidator = (linkedFormControls: LinkedFormControl) => {
return (formGroup: FormGroup): ValidationErrors | null => {
Object.keys(linkedFormControls).forEach(key => {
const { value: checkerValue } = formGroup.get(key)!;
const dependentKeys = arrayify(linkedFormControls[key]);
dependentKeys
.map(dependentKey => formGroup.get(dependentKey)!)
.forEach((dependentFormControl: FormControl) => {
dependentFormControl.setErrors(
getErrorsFor(checkerValue, dependentFormControl),
);
});
});
return null;
};
};
...调用将是这样的:
{
validator: linkedFieldValidator({
car: ['license_plate', 'mileage'],
hair: 'hair_color',
}),
},
我有一个 Angular 9 表单,其中四个字段相关。一个是复选框,其余是输入。选中复选框时,输入不应为空,但未选中时,则无所谓。我想为此制作验证器,以便仅当字段为空且第一个字段设置为 true 时才会出现错误。
我还考虑过创建一个本地布尔值来表示复选标记的状态,然后像这样将其传递给验证器。
export function linkedFieldValidator(toggler: boolean): ValidatorFn {
console.log('updated');
return (control: AbstractControl): {[key: string]: any} | null => {
return (toggler && control.value === '') ? {linkedField: {value: control.value}} : null;
};
}
...
field: new FormControl('', linkedFieldValidator(this.checkboxvalue)),
...
这不起作用,但是,我想,因为它只传递布尔值一次,之后不会更新。即使调用 updateValueAndValidity()
也不起作用,这对我来说很奇怪(如果不是这样,那么它的目的是什么?)。
我的 FormGroup
的结构看起来像这样:
this.form = this.formBuilder.group({
name: new FormControl(''), // don't care
address: new FormControl(''), // don't care
car: new FormControl(false), // do care - this is the checkmark
license_plate: new FormControl('', Validators.pattern(MY_LICENSE_PLATE_REGEX)), // shouldn't be empty when car
mileage: new FormControl('') // shouldn't be empty when car
hair: new FormControl(false), // do care - this is the checkmark
hair_color: new FormControl(''), // shouldn't be empty when hair
});
如您所见,我有几个 FormControll
相互关联,我只希望将其中几个链接起来。另一个需要注意的重要事项是,虽然如果违反其中一个条件可能会使整个表单无效,但我希望能够单独解决每个错误,以便我可以在适当的位置显示适当的消息。
我没有更多的想法,有人可以帮助我吗?我正在使用反应形式。
编辑 1:
DEMO 对子表单(输入所在的位置)和主表单进行验证
原回答:
您可以使用cross-fields validation来组合检查多个字段。这里我有 1 个复选框(初始状态为 false
或未选中)和 3 个输入字段。在我的自定义验证器中,我只是检查值并设置相应的表单验证错误:
export class ProfileEditorComponent {
myForm = new FormGroup({
'checker': new FormControl(false),
'name': new FormControl(),
'middleName': new FormControl(),
'lastName': new FormControl()
}, { validators: myCustomValidator });
constructor(private fb: FormBuilder) { }
}
export const myCustomValidator : ValidatorFn = (control: FormGroup): ValidationErrors | null => {
const checker = control.get('checker').value;
const name = control.get('name');
if (checker) {
if (name.value===null || name.value==="") {
return {'firstNameMissing': true};
// TODO: do the same for the other fields or any field combination
} else {
return null;
}
}
return null;
};
然后错误信息的显示如下:
<label>
First Name:
<input type="text" formControlName="name">
</label>
<div *ngIf="myForm.errors?.firstNameMissing && (myForm.touched || myForm.dirty)" class="cross-validation-error-message alert alert-danger">
First name required.
</div>
问题是您只将初始值传递给 linkFieldValidator
函数。
为了动态获得值,您可以通过 FormGroup
传递 linkFieldValidator
,如下所示:
readonly formGroup = this.formBuilder.group(
{
checkbox: '',
name: ''
},
{ validator: linkedFieldValidator }
);
完整样本:
import { ChangeDetectionStrategy, Component } from '@angular/core';
import {
AbstractControl,
FormBuilder,
FormGroup,
ValidationErrors,
ValidatorFn,
} from '@angular/forms';
export const linkedFieldValidator = (formGroup: FormGroup): ValidationErrors | null => {
const [checkboxFormControlValue, nameFormControlValue] = [
formGroup.get('checkbox')!.value,
formGroup.get('name')!.value
];
return checkboxFormControlValue && !nameFormControlValue
? { linkedField: { value: nameFormControlValue } }
: null;
};
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'input-overview-example',
styleUrls: ['input-overview-example.css'],
templateUrl: 'input-overview-example.html'
})
export class InputOverviewExample {
readonly formGroup = this.formBuilder.group(
{
checkbox: '',
name: ''
},
{ validator: linkedFieldValidator }
);
constructor(private readonly formBuilder: FormBuilder) {}
}
编辑 1:
如果您需要将错误驻留在每个表单控件中,您可以将 linkedFieldValidator
更改为:
export const linkedFieldValidator = (formGroup: FormGroup): null => {
const { value: checkboxFormControlValue } = formGroup.get('checkbox')!;
const inputFormControls = [
formGroup.get('input1')!,
formGroup.get('input2')!,
formGroup.get('input3')!,
];
inputFormControls.forEach(inputFormControl => {
const { value } = inputFormControl;
const errors = checkboxFormControlValue && !value ? { linkedField: { value } } : null;
inputFormControl.setErrors(errors);
});
return null;
};
注意,如果需要保留其他错误,可能需要在setErrors
之前做一些处理。
编辑 2:
对于可以有多个链接字段的通用方法,您可以这样做:
type LinkedFormControl = Record<string, string | readonly string[]>;
const arrayify = <T>(itemOrItems: T | readonly T[]): readonly T[] => {
return Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
};
const getErrorObjectSanitized = <T extends object>(obj: T): T | null => {
return Object.keys(obj).length === 0 ? null : obj;
};
const getErrorsFor = (
checkerValue: boolean,
formControl: FormControl,
): object | null => {
const { errors, value } = formControl;
const { error, ...oldErrors } = errors || {};
const processedErrors = {
...(checkerValue && !value ? { error: true } : {}),
...oldErrors,
};
return getErrorObjectSanitized(processedErrors);
};
export const linkedFieldValidator = (linkedFormControls: LinkedFormControl) => {
return (formGroup: FormGroup): ValidationErrors | null => {
Object.keys(linkedFormControls).forEach(key => {
const { value: checkerValue } = formGroup.get(key)!;
const dependentKeys = arrayify(linkedFormControls[key]);
dependentKeys
.map(dependentKey => formGroup.get(dependentKey)!)
.forEach((dependentFormControl: FormControl) => {
dependentFormControl.setErrors(
getErrorsFor(checkerValue, dependentFormControl),
);
});
});
return null;
};
};
...调用将是这样的:
{
validator: linkedFieldValidator({
car: ['license_plate', 'mileage'],
hair: 'hair_color',
}),
},