本文介绍了Angular2 表单:具有相关字段的验证器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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.

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

I could create a FormGroup with those three fields and do one custom validators...

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 (e.g 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
});

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

How would you do that with Angular2 Is it even possible ?

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

Here is an example of how the fatValidator could be implemented. 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 }
    );
}

推荐答案

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

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.

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

这将使 'city''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:

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 数组中的所有其他控件都不是必需的,否则所有控件都是必需的.

This method will make a set of form controls 'required' based on the value of the conditionalControlKey, i.e. if conditionalControlKey has a value all other controls in controlKeys Array are not required, otherwise the all are required.

我希望这对任何人来说都不会太复杂 - 我相信这些代码片段可以改进,但我觉得它们恰如其分地展示了一种解决方法.

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.

这篇关于Angular2 表单:具有相关字段的验证器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-02 03:59