I need to write a custom validation,
If nationality is 1 then passport is mandatory - this had done.
If nationality is 2 then either civilId or passport number is mandatory - needed solution for this
Point 2 how can i achieve.
So that error message should display in html like this
<span *ngIf="form.get('passportNo').errors" class="tooltiptext">{{' passportNo is required'}}</span>
this.form = this.fb.group({
civilId: [""],
nationality: [""]
passportNo: [""],
})
setExpatValidators() {
const passportNo = this.form.get('passportNo');
this.form.get('nationality').valueChanges
.subscribe(nationality => {
if(nationality == 1){
passportNo.setValidators([Validators.required]);
}
});
}
submitForm(){
if (!this.form.valid) {
return;
}
Suggest if there any other way
You can add a custom validator for your form group:
ngOnInit() {
this.form = this.fb.group({
civilId: [""],
nationality: [""],
passportNo: [""]
});
this.form.setValidators(this.formValidator());
}
formValidator(): ValidatorFn {
return (group: FormGroup): ValidationErrors => {
const nationality = group.controls.nationality;
const passportNo = group.controls.passportNo;
const civilId = group.controls.civilId;
let errors = null;
if (nationality.value === 1 && !passportNo.value) {
errors = { passportNoRequired: true };
}
if (nationality.value === 2 && !passportNo.value && !civilId.value) {
errors = { passportNoOrCivilIdRequired: true };
}
return errors;
};
}
And validate again your form:
<span *ngIf="form.errors && form.errors.passportNoOrCivilIdRequired" class="tooltiptext">passportNo or Civil Id is required</span>
Working Demo
https://stackblitz.com/edit/form-validations-ng?file=src%2Fapp%2Fform1%2Fform1.component.ts
Related
I have the following:
export const isOneFieldValid = (val: string) => {
console.log(val)
return val ? undefined : true
}
...
const validate = (field: string) => {
switch (field) {
case 'email': {
return composeValidators(isOneFieldValid, isValidEmail, hasStringeMaxLength)
}
case 'workPhone':
case 'homePhone':
case 'mobile': {
return composeValidators(isOneFieldValid, isNumber, hasNumberMaxLength)
}
default:
return undefined
}
}
...
This validates all of the 4 fields, but is there a way to validate only one field assuming that all of them are empty?
As long as there is one way to contact the user I can submit the form
sounds like you need a form level validation, not field level?
<Form
onSubmit={onSubmit}
validate={values => {
const errors = {}
if (values.email && isValidEmail && hasStringMaxLength) {
return ;
} else {
errors.email = 'Require';
}
if (writeFunctionToCheckWorkPhone) {
return;
} else {
error.workPhone = 'Require';
}
if (writeFunctionToCheckHomePhone) {
return ;
} else {
error.homePhone = 'Require';
}
if (writeFunctionToCheckMobile) {
return ;
} else {
error.mobile = 'Require';
}
return errors;
}}
...
I'm working on a React project and implementing email validation and setting the state to true when it doesn't pass and false when it does. Validation part works, but getting undefined state on second onSubmit.
A bit more detail: I'm checking the state onChange and onSubmit. onChange seems to work as expected. onSubmit does work on the first click/submit but the very next click/submit, it changes the state to 'undefined' and I have no idea why.
Best to view my codepen and start filling in the email field and checking the console as I'm logging the state.
Here's a snippet of the code:
this.state = {
inputs: {
name: '',
email: '',
message: '',
},
show: true,
errors: {
name: false,
email: false,
message: false,
},
};
validateEmail(email) {
const re = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
handleOnChange = e => {
const { name, value } = e.target;
const emailInput = e.target.value;
const emailValid = this.validateEmail(emailInput);
if (name === 'email') {
this.setState({
inputs: {
email: emailInput,
},
errors: {
email: !emailValid,
},
});
} else {
this.setState({
inputs: {
...this.state.inputs,
[name]: value,
},
errors: {
...this.state.errors,
[name]: false,
},
});
}
console.log('errors.email onChange = ' + this.state.errors.email);
};
So, why is this happening? and how can I solve?
You have missed the else condition when the field is not empty. that will remove the error object key from state, that is the one gives you the undefined error.
rewrite the handleSubmit function like this.
handleSubmit = (e, slackPost) => {
e.preventDefault();
console.log('errors.email onClick = ' + this.state.errors.email);
let inputFields = document.getElementsByClassName('form-input');
let invalidEmailMessage = document.querySelector('#invalid-email-message');
let failMessage = document.querySelector('#fail-message');
let failMessageBox = document.querySelector('.alert-fail');
// empty array to house empty field names
const emptyFieldNames = [];
// empty object to house input state
let errors = {};
// loop through input fields...
for (var i = 0; i < inputFields.length; i++) {
if (inputFields[i].value === '') {
let inputName = inputFields[i].name;
// add name to new array
emptyFieldNames.push(inputFields[i].getAttribute('name'));
// add input name and value of true to new object
errors[inputName] = true;
failMessageBox.style.display = 'block';
} else {
let inputName = inputFields[i].name;
errors[inputName] = false;
}
}
debugger;
this.setState({ errors });
if (emptyFieldNames.length > 0) {
failMessage.innerHTML =
'Please complete the following field(s): ' + emptyFieldNames.join(', ');
} else if (this.state.errors.email === true) {
invalidEmailMessage.innerHTML = 'Please enter a valid email';
} else {
console.log('For Submitted!');
}
};
I'm new to testing and starting out writing a own validation method for my Angular app. But I cant figure it out how to test it with Karma. What is the right way to set a value and test it against my validation method?
I get following error
Property 'valid' does not exist on type 'void'.
Please be patient with me but I don't grasp why I get this error.
The form
this.carForm = this.fb.group({
car: this.fb.array([
this.fb.group({
id: car.id,
properties: this.fb.group({
color: 'blue',
vinNumber: ['', ValidationService.isValidVinNumber]
})
})
]),
registrationDate: '2011-11-12',
howManyOwners: car.previousOwners,
})
});
this.carForm.valueChanges.subscribe(value => {
});
The validation method
static isValidVinNumber(formControl: FormControl) {
if (formControl && formControl.value) {
if (formControl.value.length !== 14) {
return false;
}
} else {
return null;
}
}
My test case not working
beforeEach(() => {
fixture = TestBed.createComponent(CarComponent);
component = fixture.componentInstance;
}
........
it('should be possible to post empty vin number', () => {
const iccNumber = component.carForm.get(['car', 0, 'properties', 'vinNumber']);
expect(iccNumber.valid).toBeTruthy();
});
I have written a custom validator that checks if a date is above a certain minimum date.
the code looks like this:
export function validateMinDate(min: Date): ValidatorFn {
return (c: AbstractControl) => {
if (c == null || c.value == null)
return null;
let isValid = c.value >= min;
if (isValid) {
return null;
} else {
return {
validateMinDate: {
valid: false
}
};
}
};
}
I initate my form like this
this.definitionForm = this.fb.group({
"from": [this.details.From, Validators.required],
"to": [this.details.To, [Validators.required, validateMinDate(this.details.From)]]
});
I can see that the validator is being applied, but when I console.log() my min value in the validator I can see that it equal null.
this.details.From starts at null when I initiate the form, so I assume the parameter is not dynamic and just takes the value when the form is being set?
How can I make sure the min date is being updated when a users picks a from date, and thus changes the value of this.details.From?
#Nicolas Validator takes value only once it does not look for it changes. So we can change parameters value dynamically by assigning new validator on value changes. In your case you can do in this way:
onChanges(){
var self=this;
this.definitionForm.get('from').valueChanges.subscribe(val => {
this.from=val;
this.definitionForm.controls['to'].
setValidators(Validators.compose([Validators.required,
TimeValidators.isTimeAfter(this.from)]));
})}
Here i created a separate custom validator for comparing the time. You can either use this or modify yours
import { FormControl, Validators,ValidatorFn, AbstractControl} from '#angular/forms';
export class TimeValidators extends Validators{
static isTimeBefore(timeStr : string): ValidatorFn{
return(c: AbstractControl): {[key:string]: boolean} | null => {
if(c.value!==undefined && (isNaN(c.value)) || c.value > timeStr || c.value== timeStr){
return {
'isTimeBefore':true
}
}
return null;
}
}
static isTimeAfter(timeStr : string): ValidatorFn{
return(c: AbstractControl): {[key:string]: boolean} | null => {
if(c.value!==undefined && (isNaN(c.value)) && (c.value < timeStr || c.value == timeStr)){
return {
'isTimeAfter':true
}
}
return null;
}
}
}
Call onChanges() function after you initialize your definitionForm FormGroup.
You can modify your custom validator to take function as parameter like
export function validateMinDate(min: DateFunc): ValidatorFn {
return (c: AbstractControl) => {
if (c == null || c.value == null)
return null;
let isValid = c.value >= min();
if (isValid) {
return null;
} else {
return {
validateMinDate: {
valid: false
}
};
}
};
and initiate the form like this
this.definitionForm = this.fb.group({
...
"to": [this.details.To, [Validators.required, validateMinDate(() => this.details.From)]]
});
the DateFunc is just a type that you can create like
export interface DateFunc{
(): Date
}
and expect this.details.From to return value of type Date
How I see it, would be to apply the validator on the form group, or if you have a large form, I suggest you create a nested group for from and to and apply the validator on that, since otherwise this custom validator would be fired whenever any changes happen to form. So it would mean to update the validator and formgroup to such:
this.definitionForm = this.fb.group({
"from": [this.details.From, Validators.required],
"to": [this.details.To, [Validators.required]]
}, {validator: validateMinDate()});
export function validateMinDate(): ValidatorFn {
return (c: AbstractControl) => {
if(c) {
let isValid = c.get('to').value >= c.get('from').value;
if (isValid) {
return null;
} else {
return {validateMinDate: true};
}
}
};
}
Of course there are other options as well, such as listening for change event and then do the check of the dates, if not valid, use setErrors on form.
As an alternative to the given answers, at least in Angular 6 you can pass the component as ValidatorFn argument, so you can use its properties at runtime to validate your form control.
Here is a working example:
Component declarations:
#Component({
templateUrl: './create-application.component.html'
})
export class CreateApplicationComponent implements OnInit, OnDestroy {
viewMode: ViewMode;
application: Application;
form: FormGroup;
enterprisesData: any[] = [];
Form:
this.form = this.formBuilder.group({
name: new FormControl(
{
value: (this.viewMode === ViewMode.CONSULT || this.viewMode === ViewMode.EDIT) ? this.application.name : '',
disabled: (this.viewMode === ViewMode.CONSULT || this.viewMode === ViewMode.EDIT)
},
{
validators: [ Validators.required, Validators.minLength(3), Validators.maxLength(80) ],
updateOn: 'blur'
}
),
area: new FormControl(
{
value: (this.viewMode === ViewMode.CONSULT || this.viewMode === ViewMode.EDIT) ? this.application.area : '',
disabled: (this.viewMode === ViewMode.CONSULT)
},
{
validators: [ Validators.required ]
}
),
country: new FormControl(
{
value: '',
disabled: (this.viewMode === ViewMode.CONSULT)
},
{
validators: [ applicationCountryEnterprisesValidator(this) ]
}
)
});
ValidatorFn:
export function applicationCountryEnterprisesValidator(component: CreateApplicationComponent): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} | null => {
return (component.enterprisesData && component.enterprisesData.length > 0) ? null : { noEnterprisesSelected: true };
};
}
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...
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
});
How would you do that with Angular2 Is it even possible ?
EDIT
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 }
);
}
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'])))
})
This would make any of 'city', 'lat' or 'lng' required.
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;
}
}
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.