Reputation: 281
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.
Upvotes: 4
Views: 7595
Reputation: 316
Hy, after some real use case, my solution, is have startDate & endDate as part of component class. In my case have 1 month pre-selected and works very well.
HTML
<mat-calendar
[selected]="dateRange"
[maxDate]="maxDate"
(selectedChange)="selectedChange($event)">
</mat-calendar>
TS
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';
import { MatCalendar, DateRange, MAT_DATE_RANGE_SELECTION_STRATEGY, DefaultMatCalendarRangeStrategy } from '@angular/material/datepicker';
import { hasValue } from 'common/utils'
@Component({
selector: 'app-send-report',
templateUrl: './send-report.component.html',
styleUrls: ['./send-report.component.scss'],
providers: [{
provide: MAT_DATE_RANGE_SELECTION_STRATEGY,
useClass: DefaultMatCalendarRangeStrategy
}]
})
export class SendReportComponent implements OnInit {
startDate: Date;
endDate: Date;
maxDate: Date;
@ViewChild(MatCalendar) calendar: MatCalendar<Date>;
constructor(){
this.endDate = new Date();
this.startDate = new Date(this.endDate);
this.startDate.setMonth(this.startDate.getMonth() - 1);
this.maxDate = new Date();
this.dateRange = new DateRange<Date>(this.startDate, this.endDate);
}
ngOnInit(): void { }
dateRange: DateRange<Date>;
selectedChange(m: any) {
if (hasValue(this.startDate) && hasValue(this.endDate)) {
//both has value, reset range picker interval
this.dateRange = new DateRange<Date>(m, null);
}
else {
let start = this.dateRange.start,
end = m;
if (end < start)
this.dateRange = new DateRange<Date>(end, start);
else
this.dateRange = new DateRange<Date>(start, end);
}
this.startDate = this.dateRange.start;
this.endDate = this.dateRange.end;
}
}
Upvotes: 0
Reputation: 81
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);
}
}
Upvotes: 8
Reputation: 281
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.
Upvotes: 1