Предотвращение закрытия mat-datepicker при выборе даты

У меня есть требование, чтобы пользователь мог выбрать несколько дат в datepicker, но после каждого выбора даты mat-datepicker закрывается, я хочу предотвратить закрытие mat-datepicker при выборе даты и закрыть datepicker, когда мы щелкаем за пределами datepicker или на значке календаря.

Я попытался closed событие на mat-datepicker, но выбор даты мигает при каждом выборе даты, плюс он не закрывается, когда мы щелкаем за пределами datepicker.

Вот что я пробовал:

html-код:

<input formControlName="fromDate" matInput [matDatepicker]="fromDatePicker" placeholder="From Date" [min]="minDate" readonly>
<mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
<mat-datepicker #fromDatePicker (closed)="_openCalendar(fromDatePicker)"></mat-datepicker>

машинописный код:

_openCalendar(picker: MatDatepicker<Date>) {
    picker.open();
}

3 ответа

Я предотвратил закрытие оверлея путем изменения метода MatCalendar._userSelected.

Если вы посмотрите на шаблон MatDatePickerContent, вы заметите вызов, когда испускается _userSelection. Призыв закрыть оверлей:(_userSelection)="datepicker.close()". Теперь _userSelection излучает в MatCalendar._userSelected.

datepicker.js, строка 1687, "@angular/material": "8.2.3":

_userSelected() {
    this._userSelection.emit();
}

Поскольку мне не нужно было такое поведение для всех экземпляров DatePicker в приложении, в моем компоненте я сделал следующее:

@ViewChild('picker', { static: true }) 
private picker: MatDatepicker<Date>;
private preventCloseOnSelection = true;
private readonly initCalendarUserSelected: () => void;
public model: Array<Date>;

constructor(private readonly cdr: ChangeDetectorRef) {
    this.initCalendarUserSelected = MatCalendar.prototype._userSelected;
}

public ngAfterViewInit() {
    this.picker.calendarHeaderComponent = CalendarTimeHeaderComponent;

    this.picker.openedStream
        .pipe(takeUntil(this.isUnsubscribing))
        .subscribe(() => {

            if (this.preventCloseOnSelection) {
                MatCalendar.prototype._userSelected = () => {};
            } else {
                MatCalendar.prototype._userSelected = this.initCalendarUserSelected;
            }

            this.cdr.detectChanges();
        });
}

public ngOnDestroy() {
    MatCalendar.prototype._userSelected = this.initCalendarUserSelected;
}

Если вам нужно такое поведение для всех экземпляров средства выбора даты, вы можете переопределить метод MatCalendar._userSelected где-нибудь в AppComponent и не беспокоиться о его восстановлении.

Еще один способ - сделать this.picker.close = () => { }; после изменения даты, но вы всегда должны восстанавливать ее, чтобы можно было закрыть оверлей.

Вы можете получить выбранную дату с помощью события dateChange в matDatepickerInput и создать массив с датами для нескольких значений, а для их выбора в представлении вы можете использовать вход dateClass для matDatepicker.

Посмотреть:

<input [(ngModel)]="model" matInput [matDatepicker]="picker" placeholder="Choose a date" (dateChange)="dateChanged($event)" />
<mat-datepicker-toggle matPrefix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker [dateClass]="dateClass"></mat-datepicker>

Методы:

public dateClass = (date: Date) => {
    if (this.model.map((m) => +m).indexOf(+date) !== -1) {
        return [ 'selected' ];
    }
    return [ ];
}

public dateChanged(event: MatDatepickerInputEvent<Date>) {
    if (event.value) {
        const date = event.value;
        const index = this.model.map((m) => +m).indexOf(+date);
        if (index === -1) {
            this.model.push(date);
        } else {
            this.model.splice(index, 1)
        }
    }
}

в случае выбора диапазона дат я решил эту проблему, как показано ниже:

      export class MatDatetimeComponent {
  @ViewChild(MatDateRangePicker) containerElRef;

  constructor(
    private resolver: ComponentFactoryResolver,
    private cdr: ChangeDetectorRef,
    private cdkConnectedOverlay: OverlayOutsideClickDispatcher
  ) {}

  // store prototype close object
  private selfClose: () => void;

  ngAfterViewInit() { 
    this.selfClose = this.containerElRef.close;
  }
  public onOpen() { 
    // rewrite autoclose after date is chosen
    this.containerElRef.close = () => {};

    // close calendar manually on outside click
    this.cdkConnectedOverlay._attachedOverlays[0]._outsidePointerEvents.subscribe(() => {
        // restore saved close method
        this.containerElRef.close = this.selfClose;
        this.containerElRef.close();
    });
  }
}

Поскольку другие Решения у меня не работали, я сделал следующее:

Машинописный код:

        @ViewChild(MatDatepicker) datePicker: MatDatepicker<Date>;

  constructor(
    private changeDetectorRef: ChangeDetectorRef,
    private cdkConnectedOverlay: OverlayOutsideClickDispatcher) { }

  // store prototype close object
  private selfClose: () => void;

  opened() {
    if (isNullOrUndefined(this.selfClose)) {
      this.selfClose = this.datePicker.close;
    }

    // rewrite autoclose after date is chosen
    this.datePicker.close = () => {};

    // close calendar manually on outside click
    this.cdkConnectedOverlay._attachedOverlays[0]._outsidePointerEvents.subscribe(() => {
      // restore saved close method
      this.datePicker.close = this.selfClose;
      this.selfClose = undefined;
      this.datePicker.close();
    });
  }

Код схемы:

        <mat-datepicker #picker
                  (opened)="opened()"
  ></mat-datepicker>

Может быть, это кому-то поможет.

Другие вопросы по тегам