angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.31k stars 6.72k forks source link

bug(MatDatePicker, MatStepper): Min/Max validation on Datepicker doesn't update on step change in Stepper #20114

Open patrykp57 opened 4 years ago

patrykp57 commented 4 years ago

Reproduction

https://stackblitz.com/edit/angular-9-material-starter-ctvwko

Steps to reproduce:

  1. Set dates on step one to be valid (for example: 20/07/2020 - 27/07/2020)
  2. Go to step two on mat-stepper then back to step one.
  3. Change dates to be the same (25/07/2020 - 25/07/2020)
  4. Go to step two on mat-stepper then back to step one.
  5. Change dates to be in range from first repro step and different than step 3. (for example 20/07/2020 - 23/07/2020).
  6. Error appear { "matDatepickerMin": { "min": "2020-07-24T22:00:00.000Z", "actual": "2020-07-22T22:00:00.000Z" } }. It's min date from repro step 3.

Expected Behavior

Error shouldn't appear as date is in valid range.

Actual Behavior

Datepicker doesn't revalidate form control when step is changed.

Environment

jcimoch commented 4 years ago

I've change it to this and it's working now. Probably shouldn't put formGroupName on ng-container with ngIf inside step, but didn't have time to investigate further.

<form [formGroup]="form">
<mat-horizontal-stepper [linear]="true" #stepper>
    <mat-step [stepControl]="form.get('stepOne')">
        <div [hidden]="stepper.selectedIndex !== 0" formGroupName="stepOne">
        <input
            [matDatepicker]="startDatePicker"
            [max]="form.get('stepOne.endDate').value"
            formControlName="startDate"
            matInput
            name="date">
            <mat-datepicker-toggle [for]="startDatePicker"></mat-datepicker-toggle>
            <mat-datepicker #startDatePicker></mat-datepicker>
            <mat-error *ngIf="form.get('stepOne.startDate').hasError('matDatepickerMax') || form.get('stepOne.startDate').hasError('matDatepickerMin')">
              Invalid date
            </mat-error>
        <input
            [matDatepicker]="endDatePicker"
            [min]="form.get('stepOne.startDate').value"
            formControlName="endDate"
            matInput
            name="date">
            <mat-datepicker-toggle [for]="endDatePicker"></mat-datepicker-toggle>
            <mat-datepicker #endDatePicker></mat-datepicker>
            <mat-error *ngIf="form.get('stepOne.endDate').hasError('matDatepickerMax') || form.get('stepOne.endDate').hasError('matDatepickerMin')">
              Invalid date
            </mat-error>
            <p>
            {{ form.get('stepOne.startDate').errors | json }} 
            </p>
            <p>
            {{ form.get('stepOne.endDate').errors | json }}
            </p>
            </div>
    </mat-step>
    <mat-step [stepControl]="form.get('stepTwo')">
    Test 2
    </mat-step>
  </mat-horizontal-stepper>
</form>
patrykp57 commented 4 years ago

I've change it to this and it's working now. Probably shouldn't put formGroupName on ng-container with ngIf inside step, but didn't have time to investigate further.

<form [formGroup]="form">
<mat-horizontal-stepper [linear]="true" #stepper>
    <mat-step [stepControl]="form.get('stepOne')">
        <div [hidden]="stepper.selectedIndex !== 0" formGroupName="stepOne">
        <input
            [matDatepicker]="startDatePicker"
            [max]="form.get('stepOne.endDate').value"
            formControlName="startDate"
            matInput
            name="date">
            <mat-datepicker-toggle [for]="startDatePicker"></mat-datepicker-toggle>
            <mat-datepicker #startDatePicker></mat-datepicker>
            <mat-error *ngIf="form.get('stepOne.startDate').hasError('matDatepickerMax') || form.get('stepOne.startDate').hasError('matDatepickerMin')">
              Invalid date
            </mat-error>
        <input
            [matDatepicker]="endDatePicker"
            [min]="form.get('stepOne.startDate').value"
            formControlName="endDate"
            matInput
            name="date">
            <mat-datepicker-toggle [for]="endDatePicker"></mat-datepicker-toggle>
            <mat-datepicker #endDatePicker></mat-datepicker>
            <mat-error *ngIf="form.get('stepOne.endDate').hasError('matDatepickerMax') || form.get('stepOne.endDate').hasError('matDatepickerMin')">
              Invalid date
            </mat-error>
            <p>
            {{ form.get('stepOne.startDate').errors | json }} 
            </p>
            <p>
            {{ form.get('stepOne.endDate').errors | json }}
            </p>
            </div>
    </mat-step>
    <mat-step [stepControl]="form.get('stepTwo')">
    Test 2
    </mat-step>
  </mat-horizontal-stepper>
</form>

It doesn't change anything at all. One solution is to remove *ngIf from ng-container and everything is working well, but in my application each step depend on previous. I can't initialise all components at once.

akvamor commented 4 years ago

I have the similar problem with the ngFor. When I removed one of the item form the list, validation rules for the datepicker inside of ngFor will stuck with the last provided rules. When I provide new rules calendar give an ability to select new date but it is invalid. I am using CustomDateAdapater, and validation rules are coming from the outside of the ngFor block. If I removing one more item from the list it will update to the previous validation rules, but not to the actual.

jelbourn commented 4 years ago

@crisbeto could you look into this one? Got a couple of independent escalations related to datepicker min/max. I suspect this might be related: https://stackblitz.com/edit/angular-e2hojg-hfkdyh (unable to select a date when one of the constraints is a new instance every time)

crisbeto commented 4 years ago

@jelbourn this seems like a different issue from the one you linked which is the same as https://github.com/angular/components/issues/20278.