1
votes

I want to use material calendar as a ranged inline calendar to display and insert date ranges. When using mat-date-range-picker, this is just working (but not inline). When using mat-calendar, it is working for inline, but not for ranged. But if I pass selectedRangeValue as DateRange instead of Date, the Range is displayed properly.

The only thing still missing is input...

This is the code I use now (abbreviated):

<mat-calendar (selectedChange)="selectedRangeChange($event)"
              [selected]="selectedRangeValue"
>
</mat-calendar>
selectedRangeValue: DateRange<Date> =  new DateRange<Date>(this.selectedValue.begin, this.selectedValue.end);

I have to do this because Saturn Date picker is only supported until Angular 9, and from Angular 10 onwards Material Datepicker supports date ranges. But this "inline date range calendar" I just cannot make work...

I found https://github.com/angular/components/issues/20697 where the same problem is described and a solution was found, but not written down, so that does not help.

I also tried to understand the source code of angular material datepicker, but unfortunately I still don't get it. I would appreciate any help or hints.

2

2 Answers

1
votes

I found a way to solve this. While I am not sure if this is the best way, I want to provide the solution.

I had to create a new angular component for the inline ranged calendar:

HTML Schema

<mat-calendar
  #calendar
  [selected]="selectedRangeValue"
  (selectedChange)="selectedChange($event)"
>
</mat-calendar>

Typescript

import {Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core';
import {
  DateRange, DefaultMatCalendarRangeStrategy,
  MAT_DATE_RANGE_SELECTION_STRATEGY, MatCalendar,
} from '@angular/material/datepicker';
import {isNullOrUndefined} from '../../util/is-null-or-undefined';
import moment from 'moment';

@Component({
  // tslint:disable-next-line:component-selector
  selector: 'inline-range-calendar',
  templateUrl: './inline-range-calendar.component.html',
  styleUrls: ['./inline-range-calendar.component.sass'],
  providers: [{
    provide: MAT_DATE_RANGE_SELECTION_STRATEGY,
    useClass: DefaultMatCalendarRangeStrategy
  }]
})
export class InlineRangeCalendarComponent implements OnInit {

  @ViewChild(MatCalendar) calendar: MatCalendar<Date>;

  @Input() selectedRangeValue: DateRange<Date>;
  @Output() selectedRangeValueChange = new EventEmitter<DateRange<Date>>();

  ngOnInit(): void {
 }

selectedChange($event) {
const m = moment($event);

if (!isNullOrUndefined(this.selectedRangeValue.end)) {
  const start = this.selectedRangeValue.start;
  // @ts-ignore the parser thinks that this is a date, but it is a moment.js object, so this will work
  start.set(m.toObject());
  this.selectedRangeValue = new DateRange<Date>(start, undefined);
  this.selectedRangeValueChange.emit(this.selectedRangeValue);
} else {
  const end = (!isNullOrUndefined(this.selectedRangeValue.end)) ? this.selectedRangeValue.end : moment(m);
  // @ts-ignore the parser thinks that this is a date, but it is a moment.js object, so this will work
  end.set(m.toObject());
  // @ts-ignore the parser thinks that this is a date, but it is a moment.js object, so this will work
  this.selectedRangeValue = new DateRange<Date>(this.selectedRangeValue.start, end);
  if (this.selectedRangeValue.end < this.selectedRangeValue.start)  {
    this.selectedRangeValue = new DateRange<Date>(this.selectedRangeValue.end, this.selectedRangeValue.start);
  }
  this.selectedRangeValueChange.emit(this.selectedRangeValue);
}
}

}

Hope this may help someone.

0
votes

The suggested solution has so much extra code, and more importantly it wasn't working for me. Here's a stripped working version.

HTML

<mat-calendar [selected]="selectedRangeValue"
  (selectedChange)="selectedChange($event)">
</mat-calendar>

TS

import { Component, EventEmitter, Input, Output } from '@angular/core';
import { DateRange } from '@angular/material/datepicker';

@Component({
    selector: 'inline-range-calendar',
    templateUrl: './inline-range-calendar.component.html',
})
export class InlineRangeCalendarComponent {

    @Input() selectedRangeValue: DateRange<Date> | undefined;
    @Output() selectedRangeValueChange = new EventEmitter<DateRange<Date>>();

    selectedChange(m: any) {
        if (!this.selectedRangeValue?.start || this.selectedRangeValue?.end) {
            this.selectedRangeValue = new DateRange<Date>(m, null);
        } else {
            const start = this.selectedRangeValue.start;
            const end = m;
            if (end < start) {
                this.selectedRangeValue = new DateRange<Date>(end, start);
            } else {
                this.selectedRangeValue = new DateRange<Date>(start, end);
            }
        }
        this.selectedRangeValueChange.emit(this.selectedRangeValue);
    }

}