2
votes

I am attempting to do date validation in Angular2 with FormControls. I have a start and end date. I need to validate that the start date is before the end date. Here is how I am editing the validators on the controls when their values change:

this.editForm.get('startDate').valueChanges.debounceTime(100).subscribe(
    (startDate: any) => {
        if (startDate != null) {
            this.editForm.get('startDate').setValidators([FormValidators.validateDate, FormValidators.validateStartAndCompletionDate(this.editForm.get('startDate'), this.editForm.get('completionDate'))]);
            this.editForm.get('completionDate').updateValueAndValidity();
        } else {
            this.editForm.get('startDate').clearValidators();
        }
        this.editForm.get('startDate').updateValueAndValidity();
    }
)

and

this.editForm.get('completionDate').valueChanges.debounceTime(100).subscribe(
    (completionDate: any) => {
        if (completionDate != null) {
            this.editForm.get('completionDate').setValidators([FormValidators.validateDate, FormValidators.validateStartAndCompletionDate(this.editForm.get('startDate'), this.editForm.get('completionDate'))]);
            this.editForm.get('startDate').updateValueAndValidity();
        } else {
            this.editForm.get('completionDate').clearValidators();
        }
        this.editForm.get('completionDate').updateValueAndValidity();
    }
)

Here is the validator that is setting errors on both the start and end date controls:

static validateStartAndCompletionDate(start: any, completion: any) {
        return (f: FormControl) => {
            if (start.value != null && completion.value != null) {
                var startDate = new Date(start.value.date.year, start.value.date.month - 1, start.value.date.day);
                var completionDate = new Date(completion.value.date.year, completion.value.date.month - 1, completion.value.date.day);
                if (startDate.getTime() > completionDate.getTime()) {
                    start.setErrors({
                        "invalidStartAndCompletion": true
                    });
                    completion.setErrors({
                        "invalidStartAndCompletion": true
                    });
                    return { invalidStartAndCompletion: true };
                } else {
                    start.setErrors({
                        "invalidStartAndCompletion": null
                    });
                    completion.setErrors({
                        "invalidStartAndCompletion": null
                    });
                    return null;
                }
            }
        }
    }

The validator is working correctly and is setting the errors of each FormControl element accurately. The issue comes when I have to update the validity of each FormControl after the errors are updated. For some reason, with the above code, only the element whose value is being changed is having its validity updated.

For example, if the start date is currently after the completion date (causing a validator error), and I correct the start date to make it before the completion date, the validity of the start date FormControl changes from false to true, but the validity of the completion date FormControl stays false. The opposite is also true where correcting the completion date to fix the error only makes the completion date FormControl valid, not the start date FormControl.

1
can you create a plunker for this hard to figure out like this? - Rahul Singh
Just a guess here, try to move dependant field operation after validating primary field. I.e. this.editForm.get('startDate').updateValueAndValidity(); this.editForm.get('completionDate').updateValueAndValidity(); for startDate validation. My assumption there is that startDate value is not changed yet, when you try to validate completionDate - A. Tim
This might be easier if you defined the two dates as part of a form group and then validated the group. The form validation would then automatically take care of the cross-field issues. But that would be quite a change and I'm not sure you would want to go that way. - DeborahK

1 Answers

1
votes

I would go the route that Deborah suggested. Put these dates in a separate form group and do the validation for that group, instead of subscribing to both the start date and completion date, so something like this:

this.editForm = this.fb.group({
  dates: this.fb.group({
    startDate: [new Date(), Validators.required],
    completionDate: [new Date(), Validators.required],
  },{validator: this.validateMyDates})
});  

Now the validation is on the dates, and that is also where the validation error resides when it comes to show the validation error in template.

Then the validator would look something like this:

validateMyDates(formgroup: FormGroup) { // parameter is the 'dates' formgroup
  let start = formgroup.get('startDate');
  let completion = formgroup.get('completionDate')

  if (completion < start){
     return { invalidStartAndCompletion: true };
  }
  return null;
}

As for the error message, you can reach it with:

editForm.hasError('invalidStartAndCompletion', 'dates')