Ramona
Ramona

Reputation: 255

Angular 2 form validations start date <= end date

I'm trying to add validations such that the end date can't be before the start date. Unfortunately I have no idea how to do that, and I didn't find any helpful advice in the internet so far. My form looks like this:

editAndUpdateForm(tageler: Tageler) {
    this.tageler = tageler;
    this.tagelerForm = this.fb.group({
      title: [this.tageler.title, Validators.required],
      text: this.tageler.text,
      group: [[this.tageler.group], Validators.required],
      date_start: new Date(this.tageler.start).toISOString().slice(0, 10),
      date_end: new Date(this.tageler.end).toISOString().slice(0, 10),
      ...
    });
    this.tagelerForm.valueChanges
       .subscribe(data => this.onValueChanged(data));
    }

My validations so far:

onValueChanged(data?: any) {
    if (!this.tagelerForm) {
      return;
    }
    const form = this.tagelerForm;

    for (const field in this.formErrors) {

    // clear previous error message (if any)
      this.formErrors[field] = '';
      const control = form.get(field);

      if (control && control.dirty && !control.valid) {
        const messages = this.validationMessages[field];
        for (const key in control.errors) {
          this.formErrors[field] += messages[key] + ' ';
        }
      }
    }
}

validationMessages = {
    'title': {
      'required': 'Geben Sie bitte einen Namen ein.',
    },
    'group': {
      'required': 'Wählen Sie bitte eine Gruppe aus.'
    },
    'bringAlong': {
      'required': 'Bitte Feld ausfüllen.'
    },
    'uniform': {
      'required': 'Bitte Feld ausfüllen.'
    },
};

formErrors = {
    'title': 'Geben Sie bitte einen Namen ein.',
    'group': 'Wählen Sie bitte eine Gruppe aus.',
    'bringAlong': 'Bitte Feld ausfüllen',
    'uniform': 'Bitte Feld ausfüllen',
};

The the form-controls 'date_start' & 'date_end' contain a date-string of the form 'dd.MM.yyyy', and I want 'date_end' to be bigger or equal 'date_start'.

I'd like to directly display the error message (my html code looks like this:)

<label for="formControlName_date_end" class="col-3 col-form-label">Ende:</label>
      <div class="col-5">
        <input id="formControlName_date_end" class="form-control" formControlName="date_end" type="date" value="{{tageler.end | date: 'yyyy-MM-dd'}}">
      </div>
      <div *ngIf="formErrors.date_end" class="alert alert-danger">
        {{ formErrors.date_end }}
      </div>

Could someone help me?

Thanks!

Upvotes: 20

Views: 88060

Answers (6)

Sander Oosterwijk
Sander Oosterwijk

Reputation: 439

Based on the answer of santiagomaldonado I have created a generic ValidatorFn that can be used in multiple Reactive Forms with a dynamic return value.

export class DateValidators {
    static dateLessThan(dateField1: string, dateField2: string, validatorField: { [key: string]: boolean }): ValidatorFn {
        return (c: AbstractControl): { [key: string]: boolean } | null => {
            const date1 = c.get(dateField1).value;
            const date2 = c.get(dateField2).value;
            if ((date1 !== null && date2 !== null) && date1 > date2) {
                return validatorField;
            }
            return null;
        };
    }
}

Import the validator and use it like this in your formgroup validators.

    this.form = this.fb.group({
        loadDate: null,
        deliveryDate: null,
    }, { validator: Validators.compose([
        DateValidators.dateLessThan('loadDate', 'deliveryDate', { 'loaddate': true }),
        DateValidators.dateLessThan('cargoLoadDate', 'cargoDeliveryDate', { 'cargoloaddate': true })
    ])});

Now you can use the validation in HTML.

<md-error *ngIf="form.hasError('loaddate')">Load date must be before delivery date</md-error>

Upvotes: 38

gabrielgiussi
gabrielgiussi

Reputation: 9575

You can also do it with Reactive Forms. The FormBuilder API lets you add custom validators.

Valid keys for the extra parameter map are validator and asyncValidator

Example:

import { Component }  from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'reactive-form',
  templateUrl: './reactive-form.html'
})
export class ReactiveFormComponent {
  
  form: FormGroup

  constructor(private fb: FormBuilder){
    this.createForm();
  }

  createForm() {
    this.form = this.fb.group({
      dateTo: ['', Validators.required ],
      dateFrom: ['', Validators.required ]
    }, {validator: this.dateLessThan('dateFrom', 'dateTo')});
  }
  
  dateLessThan(from: string, to: string) {
   return (group: FormGroup): {[key: string]: any} => {
    let f = group.controls[from];
    let t = group.controls[to];
    if (f.value > t.value) {
      return {
        dates: "Date from should be less than Date to"
      };
    }
    return {};
   }
 }
      
}

Note that I'm comparing the values of the inputs date and from with >, but by default this would be comparing strings. In the live example I'm using angular-date-value-accessor and importing the directive useValueAsDate.

<input formControlName="dateFrom" type="date" useValueAsDate />

With this directive group.controls[from].value and group.controls[to].value returns Date and then I can compare them with <.

Live example in plunkr

Credits to Dave's answer

Upvotes: 21

Energy
Energy

Reputation: 958

Mine is angular7 + ngprime(for calendar)

(*if you don't want ngprime just replace calendar part to others.)

Refer below code for date validation.

My code has additional validation that once start date is selected, I block previous days in end data's calendar so that the end date will be always later than that.

if you don't want to block date, delete [minDate] part. it is also working.

> Component

export class test implements OnInit {

  constructor() { }

  defaultDate: Date = new Date();
  checkDate = true;
  form: FormGroup;

  today: Date = new Date(); //StartDate for startDatetime
  startDate: Date = new Date();  //StartDate for endDatetime

  initFormControls(): void {
    this.today.setDate(this.today.getDate());
    this.startDate.setDate(this.today.getDate()); //or start date + 1 day

    this.form = new FormGroup({
      startDatetime: new FormControl('', [Validators.required]),
      endDatetime: new FormControl('', [Validators.required])
    },
      { validators: this.checkDateValidation });
  }

  checkDateValidation: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
    try {

      let startingDatefield = control.get('startDatetime').value;

      this.startDate = new Date(startingDatefield); //new Date(startingDatefield).getDate()
      let endingDatefield = control.get('endDatetime').value;

      if (this.today >= startingDatefield) { //compare to current date 
        console.log("Please set a Start Date that is on or after Current Date and Time.");
        return { 'effectiveStartDatetime': false };

      } else
        if (startingDatefield > endingDatefield && endingDatefield) {
          console.log("Please set an End Date and Time that is after the Start Date and Time.");
          return {};

        } else {
          return {};
        }
    } catch (err) {
    }
  };



  onSubmit() {

    //if form is not valid
    if (!this.form.valid) {
      console.log(" Please fill in all the mandatory fields");

      // do sth
    return;
    }


    //if form is valid
    //do sth

  }


  ngOnInit() {
    this.initFormControls();
  }

> HTML

<form [formGroup]="form" (ngSubmit)="onSubmit()">
  <div>
    <div>
      <p-button type="submit" label="submit"></p-button>
    </div>
  </div>
  <div>
    <p>Start Date/Time"</p>
    <div>
      <p-calendar formControlName="startDatetime" appendTo="body" showTime="true" hourFormat="24" stepMinute="30"
        showSeconds="false" dateFormat="yy-mm-dd" [minDate]="today"></p-calendar>
      <div
        *ngIf="form.get('startDatetime').invalid && (form.get('startDatetime').dirty || form.get('startDatetime').touched)">
        <div *ngIf="form.get('startDatetime').hasError('required')">
        </div>
      </div>
    </div>

    <p>End Date/Time</p>
    <div>
      <p-calendar formControlName="endDatetime" appendTo="body" showTime="true" hourFormat="24" stepMinute="30"
        showSeconds="false" dateFormat="yy-mm-dd" [minDate]="startDate"></p-calendar>
      <div *ngIf="form.get('endDatetime').invalid && (form.get('endDatetime').dirty || form.get('endDatetime').touched)">
        <div *ngIf="!checkDate || form.get('endDatetime').hasError('required')">
        </div>
      </div>
    </div>
  </div>

</form>

Upvotes: 0

Ivica Buljević
Ivica Buljević

Reputation: 180

I am using moment, and in angular 7 to compare and validate dates, i use this function:

datesValidator(date1: any, date2: any): {[key:string]:any} | null {
    return (group: FormGroup): { [key: string]: any } | null => {
        let start = group.controls[date1].value;
        let end = group.controls[date2].value;
        let datum1 = _moment(start).startOf('day');
        let datum2 = _moment(end).startOf('day');
        if (_moment(datum1).isSameOrAfter(datum2)) {
           this.alert.red("Error: wrong period!"); //popup message
           return { 'error': 'Wrong period!' };        
        }
        return null; //period is ok, return null
    };
}

Upvotes: 0

Darshi Angolkar
Darshi Angolkar

Reputation: 41

create a form group . Let the controls be a part of form group .

       new FormGroup({   
        startDate: this.fb.group({
            dateInput: [{value: ""}, startDateValidator()]
        }),   
        endDate: this.fb.group({
            dateInput: ["", endDateValidator()]
        })  
    }, startDateCannotBeLessThanEndDateValidator());   

        startDateCannotBeLessThanEndDateValidator(formGroup: FormGroup) {

             let startDate = formGroup.get("startDate");   
            let endDate = formGroup.get("endDate");
            // compare 2 dates 
        }

Upvotes: 4

sainu
sainu

Reputation: 2701

we cant do it in validation because we need two control values that is startdate and enddate for comparison. So it is better to compare two dates in your component

error:any={isError:false,errorMessage:''};

compareTwoDates(){
   if(new Date(this.form.controls['date_end'].value)<new Date(this.form.controls['date_start'].value)){
      this.error={isError:true,errorMessage:'End Date can't before start date'};
   }
}

In your html

<label for="formControlName_date_end" class="col-3 col-form-label">Ende:</label>
<div class="col-5">
      <input id="formControlName_date_end" class="form-control" formControlName="date_end" type="date" value="{{tageler.end | date: 'yyyy-MM-dd'}}" (blur)="compareTwoDates()">
</div>
<div *ngIf="error.isError" class="alert alert-danger">
   {{ error.errorMessage }}
</div>

Upvotes: 1

Related Questions