简体   繁体   English

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

[英]Angular2 forms : validator with interrelated fields

Given a form where one can enter either a city name or its latitude and longitude. 给定一种形式,可以输入城市名称或经度和纬度。 The form would validate if city name is filled OR if both latitude AND longitude are filled. 表格将验证城市名称是否已填写或者是否填写了纬度经度 Latitude and longitude, if filled, must be numbers. 纬度和经度,如果填写,必须是数字。

I could create a FormGroup with those three fields and do one custom validators... 我可以使用这三个字段创建一个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
});

...but I would like to take advantage of validators composition (eg testing latitude and longitude to be valid numbers at the fields level in one validator and test the interrelation at the group level in another validator). ...但我想利用验证器组合(例如,在一个验证器中测试纬度和经度是字段级别的有效数字,并在另一个验证器中测试组级别的相互关系)。

I have tested several options but I am stuck with the fact that a group is valid if all its fields are valid. 我已经测试了几个选项,但我坚持认为如果一个组的所有字段都有效,则该组有效。 The following construction seems not to be the proper way to approach the problem : 以下结构似乎不是解决问题的正确方法:

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
});

How would you do that with Angular2 Is it even possible ? 你怎么用Angular2做到这一点甚至可能吗?

EDIT 编辑

Here is an example of how the fatValidator could be implemented. 以下是如何实现fatValidator的示例。 As you can see it is not reusable and harder to test than composed validators : 正如您所看到的,它不是可重用的,而且比组合验证器更难测试:

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 }
    );
}

Using the final release or new of Angular, I have written a reusable method to add a Conditional Required- or other Validation -to a given set of Controls. 使用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 can be used in your code like this: 这可以在你的代码中使用,如下所示:

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'])))
})

This would make any of 'city' , 'lat' or 'lng' required. 这将使任何'city''lat''lng'必需。

Additionally, if you wanted either 'city' or 'lat' and 'lng' to be required you can include an additional validator such as this: 此外,如果您需要'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;
    }
}

This method will make a set of form controls ' required ' based on the value of the conditionalControlKey , ie if conditionalControlKey has a value all other controls in controlKeys Array are not required, otherwise the all are required. 此方法将根据conditionalControlKey的值生成一组表单控件' required ',即如果conditionalControlKey具有值,则不需要controlKeys Array中的所有其他控件,否则全部都是必需的。

I hope this isn't too convoluted for anyone to follow- I am sure these code snippets can be improved, but I feel they aptly demonstrate one way of going about this. 我希望这对任何人都不会过于复杂 - 我确信这些代码片段可以改进,但我觉得它们恰当地证明了这种方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM