1

I am working with Angular/Material version 19 (standalone).

When I want to display the calendar in a month view, this is my template's code:

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

enter image description here

And everything is working fine.

When I want to display the calendar for date range selection, this is the template code:

<mat-form-field>
    <mat-label>Select a date range</mat-label>
    <mat-date-range-input [formGroup]="range" [rangePicker]="picker">
        <input matStartDate formControlName="start" placeholder="Start date" readonly />
        <input matEndDate formControlName="end" placeholder="End date" readonly />
    </mat-date-range-input>
    <mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
    <mat-date-range-picker #picker></mat-date-range-picker>
</mat-form-field>

enter image description here

And everything is working fine with that too.

But now, I need them combined - I need to show the calendar in a month view, but for the user to select a date range and not just a single specific date.

How do I do that exactly? If that's possible, of course.

2
  • could you able to give some example or explanation of output Commented Dec 2, 2024 at 7:48
  • I've added screenshot images to elaborate on how each of the implementations work in my app. Hope this sheds some more light on my question here. Commented Dec 2, 2024 at 7:53

1 Answer 1

1

The month view is not working which is suspect is an angular bug, I have raised a github issue -> CALENDAR: start view month is not working hopefully it is resolved/answered.

I found this great article on setting range selection for Mat Calendar:

Using Angular Material's calendar with date ranges and range presets

We should import two providers that take care of range selection.

import {
  DateRange,
  MatDatepickerModule,
  MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER,
  DefaultMatCalendarRangeStrategy,
  MatRangeDateSelectionModel,
} from '@angular/material/datepicker';
...

...
@Component({
  selector: 'date-range-picker-forms-example',
  templateUrl: 'date-range-picker-forms-example.html',
  providers: [DefaultMatCalendarRangeStrategy, MatRangeDateSelectionModel],

After this, we can just use the strategy to determine the selection needed for the date picker.

constructor(
  private readonly selectionModel: MatRangeDateSelectionModel<Date>,
  private readonly selectionStrategy: DefaultMatCalendarRangeStrategy<Date>
) {}

// Event handler for when the date range selection changes.
rangeChanged(selectedDate: Date, callback: Function) {
  const selection = this.selectionModel.selection,
    newSelection = this.selectionStrategy.selectionFinished(
      selectedDate,
      selection
    );

  this.selectionModel.updateSelection(newSelection, this);

But we also need the form to be updated with the latest values, for this, we need to introduce a callback, which updates the form, but the selectedChange is executed inside the datepicker, so that form of the component is not visible to it, so we use .bind(this) to make sure that when the callback get's executed it has access to the component form.

<mat-calendar
  [selected]="this.selectedDateRange"
  [comparisonStart]="this.selectedDateRange!.start"
  [comparisonEnd]="this.selectedDateRange!.end"
  (selectedChange)="this.rangeChanged($event, setFormRangeControls.bind(this))"
></mat-calendar>

So the final callback sets the form values.

setFormRangeControls() {
  this.range.setValue({
    start: this.selectedDateRange?.start || null,
    end: this.selectedDateRange?.end || null,
  });
}

HTML:

<mat-form-field>
  <mat-label>Enter a date range</mat-label>
  <mat-date-range-input [formGroup]="range">
    <input matStartDate formControlName="start" placeholder="Start date" />
    <input matEndDate formControlName="end" placeholder="End date" />
  </mat-date-range-input>
  <mat-hint>MM/YYYY – MM/YYYY</mat-hint>
  @if (range.controls.start.hasError('matStartDateInvalid')) {
  <mat-error>Invalid start date</mat-error>
  } @if (range.controls.end.hasError('matEndDateInvalid')) {
  <mat-error>Invalid end date</mat-error>
  }
</mat-form-field>
<mat-calendar
  [selected]="this.selectedDateRange"
  [comparisonStart]="this.selectedDateRange!.start"
  [comparisonEnd]="this.selectedDateRange!.end"
  (selectedChange)="this.rangeChanged($event, setFormRangeControls.bind(this))"
></mat-calendar>

TS:

import { JsonPipe } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import {
  FormControl,
  FormGroup,
  FormsModule,
  ReactiveFormsModule,
} from '@angular/forms';
import { provideNativeDateAdapter } from '@angular/material/core';
import {
  DateRange,
  MatDatepickerModule,
  MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER,
  DefaultMatCalendarRangeStrategy,
  MatRangeDateSelectionModel,
} from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field';

import * as _moment from 'moment';
// tslint:disable-next-line:no-duplicate-imports
import { default as _rollupMoment, Moment } from 'moment';
import { MatInputModule } from '@angular/material/input';
import { provideMomentDateAdapter } from '@angular/material-moment-adapter';

const moment = _rollupMoment || _moment;
// See the Moment.js docs for the meaning of these formats:
// https://momentjs.com/docs/#/displaying/format/
export const MY_FORMATS = {
  parse: {
    dateInput: 'MM/YYYY',
  },
  display: {
    dateInput: 'MM/YYYY',
    monthYearLabel: 'MMM YYYY',
    dateA11yLabel: 'LL',
    monthYearA11yLabel: 'MMMM YYYY',
  },
};

/** @title Date range picker forms integration */
@Component({
  selector: 'date-range-picker-forms-example',
  templateUrl: 'date-range-picker-forms-example.html',
  providers: [DefaultMatCalendarRangeStrategy, MatRangeDateSelectionModel],
  imports: [
    MatFormFieldModule,
    MatDatepickerModule,
    FormsModule,
    ReactiveFormsModule,
    JsonPipe,
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DateRangePickerFormsExample {
  readonly range = new FormGroup({
    start: new FormControl<Date | null>(null),
    end: new FormControl<Date | null>(null),
  });
  selectedDateRange: DateRange<Date | null> | undefined = new DateRange(
    null,
    null
  );

  constructor(
    private readonly selectionModel: MatRangeDateSelectionModel<Date>,
    private readonly selectionStrategy: DefaultMatCalendarRangeStrategy<Date>
  ) {}

  // Event handler for when the date range selection changes.
  rangeChanged(selectedDate: Date, callback: Function) {
    const selection = this.selectionModel.selection,
      newSelection = this.selectionStrategy.selectionFinished(
        selectedDate,
        selection
      );

    this.selectionModel.updateSelection(newSelection, this);
    // sync the selection the form controls
    this.selectedDateRange = new DateRange<Date>(
      newSelection.start,
      newSelection.end
    );
    if (callback) {
      callback();
    }
  }

  setFormRangeControls() {
    this.range.setValue({
      start: this.selectedDateRange?.start || null,
      end: this.selectedDateRange?.end || null,
    });
  }
}

Stackblitz Demo

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for the reply and details, but in this case the calendar is still not shown in an open month view, and the eventual selection by the user is for month range. That's not what I meant. I need the calendar to be always open in month view (like in the first code and screenshot I included), and allow the user to select a date range (day, month, and year, for start and end), like in the second code and screenshot I included.
@TheCuBeMan updated my answer

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.