2

I need to create a form that will contain 2 dates, dateFrom and dateTo. The condition for validation is that dateFrom cannot be after dateTo and dateTo cannot be before dateFrom.

So I created a form group with two form controls and a validator in common that will check this condition.

export class DateRangeSelector {

    dateForm: FormGroup = new FormGroup({
         dateFrom: new FormControl({ year: 2017, month: 10 },[this.dateValidator.bind(this)]),
         dateTo: new FormControl({ year: 2020, month: 11 }, [this.dateValidator.bind(this)])
    });

dateValidator(control: FormControl): { [s: string]: boolean } {
    const valueDateFrom = this.dateForm.get('dateForm').value;
    const valueDateTo = this.dateForm.get('dateTo').value;
    if (valueDateFrom && valueDateTo) {
        //please ignore the fact that value is {year: x, month: y}, I need  to parse
        const dateFrom = moment(valueDateFrom);
        const dateTo = moment(valueDateTo);
        if (dateFrom.isAfter(dateTo)) {
            return { invalidDate: true };
        }
    }
    return null;
    }
}

My problem is that this.dateForm is not defined (not in the context) when validators try to validate. And I don't understand because I binded the method in the validators declaration.

2

3 Answers 3

3

you can define your control as

new FormGroup({
        dateFrom: new FormControl('', [validateStartResult]),
        dateTo: new FormControl('', [validateEndResult]),
})

then create separate file for validation

import {AbstractControl, ValidationErrors, ValidatorFn} from "@angular/forms";

enum ValidationFor {
    end = 'end',
    start = 'start'
}

export const validateEndResult: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
    return validateResult(control, ValidationFor.end);
}
export const validateStartResult: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
    return validateResult(control, ValidationFor.start);
}

function validateResult(control, currentValidation: ValidationFor): ValidationErrors | null {
    const startControl = control?.root?.get('dateFrom');
    const endControl = control?.root?.get('dateTo');

    const currentTime = new Date().getTime();
    const startTimeStamp = new Date(startControl?.value).getTime(); // configurations yours date
    const endTimeStamp = new Date(endControl?.value).getTime(); // configurations yours date

    const error: { [key: string]: boolean } = {};
    let hasError = false;

    if (startTimeStamp > endTimeStamp) { // main codition
        if (currentValidation === ValidationFor.end) {
            error.endResult = true
        }
        if (currentValidation === ValidationFor.start) {
            error.startResult = true
        }
        hasError = true;
    }

    return hasError ? error : null;
}

you can follow this guide to complete own validation

2

I believe the reason this.dateForm comes as undefined because the Validatdor gets executed while creation of the individual Form Controls itself, i.e. when the property dateForm is still under creation.

You can validate this by doing this in your validator in existing code.

  dateValidator() {
    console.log(this.dateForm);
  }

You would see the group geting printed after two undefined

A good approach to handle this would be use a group validatdor, like:

dateForm: FormGroup = new FormGroup(
  {
    dateFrom: new FormControl({ year: 2017, month: 10 }),
    dateTo: new FormControl({ year: 2020, month: 11 }),
  },
  this.dateValidator.bind(this)
);

dateValidator(group: FormGroup) {
  const valueDateFrom = group.get('dateFrom').value;
  const valueDateTo = group.get('dateTo').value;
  if (valueDateFrom && valueDateTo) {
    //please ignore the fact that value is {year: x, month: y}, I need  to parse
    const dateFrom = new Date(valueDateFrom).getTime(); // moment(valueDateFrom)
    const dateTo = new Date(valueDateTo).getTime(); // moment(valueDateTo);
    if (dateFrom > dateTo) {
      console.log('Error =====> ');
      return { invalidDate: true };
    }
  }

  console.log('Success =====> ');
  return null;
}

Example

0

After checking formGroup validators:

dateForm: FormGroup;

dateForm: FormGroup;

constructor(formBuilder: FormBuilder, private toastService: ToastService) {
    this.dateForm = formBuilder.group({
        dateFrom: { year: 2017, month: 10 },
        dateTo: { year: 2018, month: 11 }
    });
    this.dateForm.setValidators(this.dateValidator());
}

dateValidator(): ValidatorFn {
    return (group: FormGroup): ValidationErrors => {
        const controlDateFrom = group.controls.dateFrom;
        const controlDateTo = group.controls.dateTo;
        if (controlDateFrom.value && controlDateTo.value) {
            const dateFrom = moment(controlDateFrom.value);
            const dateTo = moment(controlDateTo.value);
            if (dateFrom.isAfter(dateTo)) {
                controlDateFrom.setErrors({ invalidDateRange: true});
                controlDateFrom.setErrors({ invalidDateRange: true});
            }else{
                controlDateFrom.setErrors(null);
                controlDateTo.setErrors(null);
            }
        }
        return;
    };
}

This solution is particulary interesting because as it is binded to the component I can show a toast for example when the date is wrong. Or I can format the date value before it's setted and this way avoid all errors.

Not the answer you're looking for? Browse other questions tagged or ask your own question.