Reputation: 4756
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
?
Upvotes: 14
Views: 21135
Reputation: 11
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 };
};
}
Upvotes: 1
Reputation: 231
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
Upvotes: 14
Reputation: 2111
@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
.
Upvotes: 16
Reputation: 73357
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.
Upvotes: 5