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

时间:2016-07-05 13:30:50

标签: javascript angular angular2-forms

给出一个可以输入城市名称或经度和纬度的表格。 表单将验证城市名称是否已填充或者纬度 AND 经度是否已填充。纬度和经度,如果填写,必须是数字。

我可以使用这三个字段创建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 }
    );
}

1 个答案:

答案 0 :(得分:0)

使用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;
    }
}

此方法将制作一组表单控件&#39; required&#39;基于conditionalControlKey的值,即如果conditionalControlKey具有值,则controlKeys数组中的所有其他控件都不是必需的,否则全部都是必需的。

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