Angular2 形式:具有相关字段的验证器

Angular2 forms : validator with interrelated fields

本文关键字:字段 验证 形式 Angular2      更新时间:2023-09-26

给定一个表格,可以在其中输入城市名称或其纬度和经度。该表单将验证是否填写了城市名称,或者是否同时填写了纬度经度。纬度和经度(如果已填写)必须是数字。

我可以使用这三个字段创建一个FormGroup并执行一个自定义验证器......

function fatValidator(group: FormGroup) { 
    // if cityName is present : is valid
    // else if lat and lng are numbers : is valid
    // else : is not valid
}
builder.group({
    cityName: [''],
    lat: [''],
    lng: ['']
},
{
    validators: fatValidator
});

。但我想利用验证器的组合(例如,在一个验证器中测试纬度和经度是否为字段级别的有效数字,并在另一个验证器中测试组级别的相互关系)。

我已经测试了几个选项,但我坚持这样一个事实,即如果组的所有字段都有效,则该组是有效的。以下构造似乎不是解决问题的正确方法:

function isNumber(control: FormControl) { ... }
function areAllFilled(group: FormGroup) { ... }
function oneIsFilledAtLeast(group: FormGroup) { ... }
builder.group({
    cityName: [''],
    localisation: builder.group({
        lat: ['', Validators.compose([Validators.minLength(1), isNumber])],
        lng: ['', Validators.compose([Validators.minLength(1), isNumber])]
    },
    {
        validators: areAllFilled
    })
},
{
    validators: oneIsFilledAtLeast
});

你会如何使用 Angular2 做到这一点,甚至可能吗?

编辑

下面是如何实现fatValidator的示例。如您所见,它不可重用,并且比组合验证器更难测试:

function fatValidator (group: FormGroup) {
    const coordinatesValidatorFunc = Validators.compose([
        Validators.required, 
        CustomValidators.isNumber
    ]);
    const cityNameControl = group.controls.cityName;
    const latControl = group.controls.lat;
    const lngControl = group.controls.lng;
    const cityNameValidationResult = Validators.required(cityNameControl);
    const latValidationResult = coordinatesValidatorFunc(latControl);
    const lngValidationResult = coordinatesValidatorFunc(lngControl);
    const isCityNameValid = !cityNameValidationResult;
    const isLatValid = !latValidationResult;
    const isLngValid = !lngValidationResult;
    if (isCityNameValid) {
        return null;
    }
    if (isLatValid && isLngValid) {
        return null;
    }
    if (!isCityNameValid && !isLatValid && !isLngValid) {
        return { cityNameOrCoordinatesRequired: true, latAndLngMustBeNumbers: true };
    }
    return Object.assign({}, 
        { cityName: cityNameValidationResult }, 
        { lat: latValidationResult }, 
        { lng: lngValidationResult }
    );
}

使用Angular的最终版本或新版本,我编写了一个可重用的方法,将条件必需或其他验证添加到给定的控件集。

export class CustomValidators {
    static controlsHaveValueCheck(controlKeys: Array<string>, formGroup: FormGroup): Array<boolean> {
        return controlKeys.map((item) => {
            // reset any errors already set (ON ALL GIVEN KEYS).
            formGroup.controls[item].setErrors(null);
            // Checks for empty string and empty array.
            let hasValue = (formGroup.controls[item].value instanceof Array) ? formGroup.controls[item].value.length > 0 :
                !(formGroup.controls[item].value === "");
            return (hasValue) ? false : true;
        });
    }
    static conditionalAnyRequired(controlKeys: Array<string>): ValidatorFn {
        return (control: FormControl): {[key: string]: any} => {
            let formGroup = control.root;
            if (formGroup instanceof FormGroup) {
                // Only check if all FormControls are siblings(& present on the nearest FormGroup)
                if (controlKeys.every((item) => {
                        return formGroup.contains(item);
                    })) {
                    let result = CustomValidators.controlsHaveValueCheck(controlKeys, formGroup);
                    // If any item is valid return null, if all are invalid return required error.
                    return (result.some((item) => {
                        return item === false;
                    })) ? null : {required: true};
                }
            }
            return null;
        }
    }
}

这可以像这样在代码中使用:

this.form = new FormGroup({
    'cityName': new FormControl('', 
        CustomValidators.conditionalAnyRequired(['cityName', 'lat', 'lng'])),
    'lat': new FormControl('', 
        Validators.compose([Validators.minLength(1),
            CustomValidators.conditionalAnyRequired(['cityName', 'lat', 'lng']))),
    'lng': new FormControl('', 
        Validators.compose([Validators.minLength(1),
            CustomValidators.conditionalAnyRequired(['cityName', 'lat', 'lng'])))
})

这将使任何'city''lat''lng'成为必需的。

此外,如果您希望需要'city''lat''lng',则可以包含额外的验证器,如下所示:

static conditionalOnRequired(conditionalControlKey: string, controlKeys: Array<string>): ValidatorFn {
    return (control: FormControl): {[key: string]: any} => {
        let formGroup = control.root;
        if (formGroup instanceof FormGroup) {
            if (controlKeys.every((item) => {
                    return formGroup.contains(item);
                }) && formGroup.contains(conditionalControlKey)) {
                let firstControlHasValue = (formGroup.controls[conditionalControlKey].value instanceof Array) ? formGroup.controls[conditionalControlKey].value.length > 0 :
                        !(formGroup.controls[conditionalControlKey].value === ""),
                    result = CustomValidators.controlsHaveValueCheck(controlKeys, formGroup);
                formGroup.controls[conditionalControlKey].setErrors(null); // Also reset the conditional Control...
                if (firstControlHasValue && formGroup.controls[conditionalControlKey].value !== false) {// also checks for false (for unchecked checkbox value)...
                    return (result.every((invalid) => {
                        return invalid === false;
                    })) ? null : {required: true};
                }
            }
        }
        return null;
    }
}

此方法将根据conditionalControlKey的值创建一组表单控件"required",即如果conditionalControlKey有一个值,则不需要controlKeys数组中的所有其他控件,否则都需要全部。

我希望这对任何人都不会太复杂 - 我相信这些代码片段可以改进,但我觉得它们恰当地展示了一种解决这个问题的方法。