I have two dates like one arrivaldate and exitdate .Trying to add custom validation like the exit date not be less than arrival date if its less show error message.Please find the below code.
component.ts file:
arrivalDate: ['', [Validators.required, this.validateArrivalDate()]],
exitDate: ['', [Validators.required, this.validateExitDate()]],
validateExitDate(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
if (this.currentTravelFormGroup !== undefined) {
//const arrivalDate = control.value;
const exitDate = this.currentTravelFormGroup.controls['exitDate'].value;
const arrivalDate = this.currentTravelFormGroup.controls['arrivalDate'].value
if (exitDate <= arrivalDate) return { requiredToDate: true };
}
};
}
validateArrivalDate(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
if (this.currentTravelFormGroup !== undefined) {
const exitDate = this.currentTravelFormGroup.controls['exitDate'].value;
const fexitDate = new Date(exitDate);
const arrivalDate = this.currentTravelFormGroup.controls['arrivalDate'].value;
if (fexitDate <= arrivalDate) return { requiredFromDate: true };
}
};
}
In Html I'm showing error message:
<mat-error *ngIf="currentTravelFormGroup.get('arrivalDate').hasError('requiredFromDate')">Please provide a valid arrival date</mat-error>
<input class="form-control bgColor" [matDatepicker]="pickerExitDateFromGermany" placeholder="MM/DD/YYYY" [min]="minStartDateFutureTravel" [max]="maxStartDateFutureTravel" formControlName="exitDate" id="exitDate" readonly (click)="pickerExitDateFromGermany.open()"
[ngClass]="{ 'is-invalid': submitted && travelDet.exitDate.errors }">
<mat-datepicker #pickerExitDateFromGermany></mat-datepicker>
<mat-error *ngIf="currentTravelFormGroup.get('exitDate').hasError('requiredToDate')">Please provide a valid exitdate</mat-error>
The condition works and shows the error message respectively for exit and arrival date but.
if arrival date is 11/11/2019 and exit date is 10/11/2019(error message will be shown below exit input field).If i change arrival date as 08/11/2019 (arrival date
what is the problem .how do i solve it.Please help
I solved this problem by clear ,setting and updating values after validateExitDate() and validateArrivalDate() methods.
validateExitDate(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
if (this.currentTravelFormGroup !== undefined) {
//const arrivalDate = control.value;
const exitDate = this.currentTravelFormGroup.controls['exitDate'].value;
const arrivalDate = this.currentTravelFormGroup.controls['arrivalDate'].value
if (exitDate <= arrivalDate) return { requiredToDate: true };
else{
this.currentTravelFormGroup.get('exitDate ').clearValidators();
this.currentTravelFormGroup.get('exitDate').updateValueAndValidity();
}
}
};
}
Same with Arrivaldate function.:)
Related
I'm trying to write cross-field -validation using Abstract Control.
control.value[start] is showing undefined. kindly help me on solving.
working code is here.
app.component.html
export class AppComponent {
form: FormGroup;
rangeStart = new FormControl('', {
validators: [this.MyAwesomeRangeValidator('rangeStart')],
});
rangeEnd = new FormControl('', {
validators: this.MyAwesomeRangeValidator('rangeEnd'),
});
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
rangeStart: this.rangeStart,
rangeEnd: this.rangeEnd,
});
}
MyAwesomeRangeValidator(rangeStart: string): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const start = control.value[rangeStart];
const end = control.value;
console.log('start', start, 'end', end);
let re = /^[1-9]\d*$/;
if (re.test(start) && re.test(end)) {
return null;
} else if (re.test(start) && !re.test(end)) {
return null;
} else if (!re.test(start) && re.test(end)) {
return null;
} else {
return {
range: true
};
}
};
}
}
<form [formGroup]="form">
<input formControlName="rangeStart" placeholder="Range start" type="number">
<input formControlName="rangeEnd" placeholder="Range end" type="number">
</form>
<div> Valid: {{ form.valid ? '👍' : '👎' }} </div>
working code
You are trying to get property 'rangeStart' from string.
const start = control.value[rangeStart];, where control.value is a string. If you want to validate few fields at once then you should put validator to the FormGroup.
export class AppComponent {
form: FormGroup;
rangeStart = new FormControl('', {
validators: [this.MyAwesomeRangeValidator('rangeStart', 'rangeEnd')],
});
rangeEnd = new FormControl('', {
validators: this.MyAwesomeRangeValidator('someField_1', 'someField_2'),
});
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
rangeStart: this.rangeStart,
rangeEnd: this.rangeEnd,
});
}
MyAwesomeRangeValidator(
startControlName: string,
endControlName: string
): ValidatorFn {
return (formGroup: FormGroup): ValidationErrors | null => {
const start = formGroup.get(startControlName).value;
const end = formGroup.get(endControlName).value;
console.log(formGroup, 'start', start, 'end', end);
let re = /^[1-9]\d*$/;
if (!re.test(start) && !re.test(end)) {
return { range: true };
}
return null;
};
}
}
UPD: added dynamic form controls names
I have form fields represented as objects that get mapped over and based upon the type returns React elements:
import { FieldProps } from "~types";
const fields: FieldProps[] = [
{ type: "text", name: "username", value: "", required: true, ...other props },
{ type: "password", name: "password", value: "", required: true, ...other props },
...etc
]
export default fields;
The problem I'm running into is I'm trying to validate the fields after a form is submitted and check for any errors in value:
handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const { validatedFields, errors } = fieldValidator(this.state.fields);
if(errors) {
this.setState({ errors });
return;
}
...etc
}
but this reusable validate function has a ts error:
import isEmpty from "lodash.isempty";
/**
* Helper function to validate form fields.
*
* #function
* #param {array} fields - an array containing form fields.
* #returns {object} validated/updated fields and number of errors.
* #throws {error}
*/
const fieldValidator = <
T extends Array<{ type: string; value: string; required?: boolean }>
>(
fields: T,
): { validatedFields: T; errors: number } => {
try {
if (isEmpty(fields)) throw new Error("You must supply an array of form fields to validate!");
let errorCount: number = 0;
// this turns the "validatedFields" array into an { errors: string; type: string; name:
// string; value: string; required?: boolean | undefined;}[] type, when it needs to be "T",
// but "T" won't be inferred as an Array with Object props: type, value, required, value defined within it
const validatedFields = fields.map(field => {
let errors = "";
const { type, value, required } = field;
if ((!value && required) || (isEmpty(value) && required)) {
errors = "Required.";
} else if (
type === "email" &&
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(field.value)
) {
errors = "Invalid email.";
}
if (errors) errorCount += 1;
return { ...field, errors };
});
return { validatedFields, errors: errorCount }; // TS error here
} catch (err) {
throw String(err);
}
};
export default fieldValidator;
Since validatedFields turns into:
{
errors: string;
name: string;
value: string;
type: string;
required: boolean;
}[]
and it's returned { validatedFields, errors }, it throws this TS error:
Type '{ errors: string; type: string; value: string; required?: boolean | undefined; }[]' is not assignable to type 'T'.
'{ errors: string; type: string; value: string; required?: boolean | undefined; }[]' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ type: string; value: string; required?: boolean | undefined; }[]'.ts(2322)
index.ts(17, 6): The expected type comes from property 'validatedFields' which is declared here on type '{ validatedFields: T; errors: number; }'
Is there a way to infer T as an array of objects that expects at least 4 (or more) properties, but returns the same typed array (just with an updated errors property)?
Typescript Playground
Is this what you are searching for ?
type EssentialField = { type: string; value: string; required?: boolean }[];
Typescript use duck typing.
type FieldProps = {
className?: string,
disabled?: boolean,
errors?: string,
placeholder?: string,
label: string,
// onChange?: (e: React.ChangeEvent<any>) => void;
name: string,
type: string,
readOnly?: boolean,
required: boolean,
value: string
styles?: object
};
const fields: FieldProps[] = [
{
type: "text",
label: "Username",
name: "username",
errors: "",
value: "",
required: true,
},
{
className: "example",
type: "password",
label:"Passowrd",
name: "password",
errors: "",
value: "",
required: true,
styles: { width: "150px" },
}
];
type EssentialField = { type: string; value: string; required?: boolean }[];
const fieldValidator = (
fields: EssentialField,
): { validatedFields: EssentialField; errors: number } => {
try {
if (!fields || fields.length < 0) throw new Error("You must supply an array of fields!");
let errorCount: number = 0;
const validatedFields = fields.map(field => {
let errors = "";
const { type, value, required } = field;
if ((!value && required) || ((value && value.length < 0) && required)) {
errors = "Required.";
} else if (
type === "email" &&
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(field.value)
) {
errors = "Invalid email.";
}
if (errors) errorCount += 1;
return { ...field, errors };
});
return { validatedFields, errors: errorCount };
} catch (err) {
throw String(err);
}
};
const { validatedFields, errors } = fieldValidator(fields)
console.log(validatedFields)
console.log(errors);
This remove the error, without usage of as any or other tricks.
EDIT: Maybe you want something like that:
type EssentialField = ({ type: string; value: string; required?: boolean }&{[key: string]: any})[];
// this allow you to do:
validatedFields[0].placeholder; // ...
// or
validatedFields[0].someUnknowProperty; // ...
This is intersecting the type with an associative array ({[key: string]: any})). Usually accessed like that associativeArray['someKey'], but it can also be used like that associativeArray.someKey.
The benefit is that your IDE should propose you the type and required field in auto-completion.
I managed to figure it out by extending T to any[] and then defining the result of validatedFields as T to preserve the passed in types:
const fieldValidator = <
T extends any[]
>(
fields: T,
): { validatedFields: T; errors: number } => {
try {
if (!fields || fields.length < 0) throw new Error("You must supply an array of fields!");
let errorCount: number = 0;
const validatedFields = fields.map(field => {
let errors = "";
const { type, value, required }: Pick<BaseFieldProps, "type" | "value" | "required"> = field;
if ((!value && required) || ((value && value.length < 0) && required)) {
errors = "Required.";
} else if (
type === "email" &&
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)
) {
errors = "Invalid email.";
}
if (errors) errorCount += 1;
return { ...field, errors };
}) as T;
return { validatedFields, errors: errorCount };
} catch (err) {
throw String(err);
}
};
Typescript playground
In the component, I use formBuilder for Angular Reactive Forms
I get the example code from this post
min = 10;
max = 20;
ngOnInit() {
this.loginForm = new FormGroup({
email: new FormControl(null, [Validators.required]),
password: new FormControl(null, [Validators.required, Validators.maxLength(8)]),
age: new FormControl(null, [ageRangeValidator(this.min, this.max)])
});
}
The custom validation is defined in function ageRangeValidator
function ageRangeValidator(min: number, max: number): ValidatorFn {
return (control: AbstractControl): { [key: string]: boolean } | null => {
if (control.value !== undefined && (isNaN(control.value) || control.value < min || control.value > max)) {
return { 'ageRange': true };
}
return null;
};
}
In case, min and max are got from api call. How can to pass them to function custom validator?
FormControl API provide setValidators method. Which you can use to set Validator dynamically. Inside subscribe call back you can call setValidators something like this:
Try this:
..subscribe((result)=>{
this.age.setValidators([ageRangeValidator(result.min,result.max)]);
this.loginForm.get('age').updateValueAndValidity();
})
Example
I prefer the answer of Chellappan, but if you are going to change the value of max and min along the live of app angular, another way is that the function validator belong to the component and bind to this
//this function IN the component
ageRangeValidator(): ValidatorFn {
return (control: AbstractControl): { [key: string]: boolean } | null => {
if (control.value !== undefined && (isNaN(control.value) ||
control.value < this.min ||control.value > this.max)
) {
return { ageRange: true };
}
return null;
};
}
//and when create the component
age: new FormControl(15, [this.ageRangeValidator().bind(this)]) //<--see the bind(this)
I have the following code that works, but the moment I change the if to p instanceof Address the condition is never met.
p is an object but I want to check it against its type that has already been defined:
export default class Address {
public addressLine1: string = ''
public addressLine2: string | null = null
public town: string = ''
public postcode: string = ''
}
const displayResults = (list: any) => {
const slicedList = list.slice(0, 3)
const html =
slicedList.map((p: string | Address) => {
console.log('p: ', p instanceof Address)
if (typeof p === 'object') {
return <span key={`id-${uniqid()}`}>{addressToString(p)}</span>
}
return <span key={`id-${uniqid()}`}>{p}</span>
})
if (list.length > 3) {
html.push(elipsis)
}
return html
}
I am going to implement the validation in angular 4.
I've already have a solution but need simple and clean code.
Here is my code.
mobile-auth.component.ts
this.appForm = fb.group({
'otp': ['', Validators.compose([Validators.required, ValidationService.digit4Validator])],
});
validation.service.ts
...
static digit4Validator(control) {
if (control.value) {
if (control.value.match(/^\d+/) && control.value.length === 4) {
return null;
} else {
return {'invalid4DigitCode': true};
}
}
}
...
If you have any good solution please let me know.
Your validator should only validate that it's a valid number. Angular already provides a minlength validator which should be used instead.
You can refactor your validator as this:
export function digitValidator(length: number): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => {
if (control.value) {
if (control.value.match(/^\d+/) && control.value.length === length) {
return null;
} else {
return {`invalidDigitCode, length should be ${length}`: true};
}
}
};
}
...
this.appForm = fb.group({
'otp': ['', Validators.compose([Validators.required,
Validators.digitValidator(4)])
...
Or you can use like this:
this.appForm = fb.group({
'otp': ['', Validators.compose([Validators.required,
Validators.minLength(4),
Validators.maxLength(4)])],
});