Angular2 forms: interlinked field validator

For the form in which you can enter the name of the city or its latitude and longitude. The form will check if the name of the city will be filled OR if filled as latitude and longitude . Latitude and longitude, if filled, must be numbers.

I could create a FormGroup with these three fields and make one custom validator ...

 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 use the composition of validators (for example, check latitude and longitude to be valid field level numbers in one validator and check group level relationships in another validator).

I checked several options, but I was stuck in the fact that a group is valid if all its fields are valid. Perhaps the following design is not suitable for solving this 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 do you do it with Angular2 Is this possible?

EDIT

Here is an example of how fatValidator can be implemented. As you can see, this is impossible to repeat and more difficult to verify than compiled 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 } ); } 
+1
javascript angular angular2-forms
source share
1 answer

Using the final release or the new Angular, I wrote a reuse method to add conditional mandatory or other confirmation - to a specific 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 as follows:

 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 will result in the need for any of 'city' , 'lat' or 'lng' .

In addition, if you want 'city' or 'lat' and 'lng' to be needed, you can include an additional validator, for example:

 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 create a set of form controls <<29> based on the value of conditionalControlKey , i.e. if conditionalControlKey matters, all other controls in the controlKeys Array are not required, otherwise all are required.

I hope this is not too difficult for everyone to follow - I am sure that these pieces of code can be improved, but I feel that they definitely demonstrate one way to do this.

0
source share

All Articles