Angular Material Calendar Component

Angular Material Calendar Component

As of version Angular Material 7.1, there is still no mention of the calendar component in the official Angular Material docs. The form controls include a datepicker component, which internally uses a calendar popup for date entry. However, if you want to display a calendar that is always open, the date picker is not feasible.

In this post, I will show you how to use the calendar component that is available as part of the Angular Material library. I will also show how to use it to get date input from the user through its events. We’ll wrap up by creating a reusable component for the calendar control.

Calendar Component

Angular Material docs demonstrate a lot of the date picker functionality. This is the mat-datepicker control, which renders an input that can accept date input in a couple of ways:

  1. Manual date value entry in the input
  2. Ability to pick a date value using the calendar popup, where the user must click inside the input to popup the calendar

While the date picker control is a very useful form component, it is not always the best way to get date value from the user. I was faced with a requirement where I had to display a calendar that is always open. The user would click on a date and update certain values in the UI change corresponding to the value selected.

Having went through the list of components, I could not find anything about a stand-alone calendar. I continued to dig deeper into the date picker.. and figured out that the MatDatePickerModule brings along a component called mat-calendar, which is shown in the popup of the date picker.

With that said, mat-calendar component is fully-functional with date, month & year selection. This is what it looks like:

Calendar Component in Angular App
Angular Material Calendar Component

How to Use the Calendar Component in Angular

Follow the steps below to use the Material Calendar component:

  1. If you don’t have a project, create a new Angular project with Material components

    ng new hello-world
    cd hello-world
    npm install @angular/material @angular/cdk @angular/animations

  2. Install Moment.js and Material Moment Adapter from npm

    npm install moment
    npm install @angular/material-moment-adapter
    I prefer using Moment.js for date manipulation because it is very lightweight and offers advanced features. If you don’t want to use momentjs, read more about Angular’s native date implementation here.

  3. Import MatDatePickerModule and MatMomentDateModule

    If you have followed step 1, add these to the CustomMaterialModule. Otherwise, import them into app.module.ts.

You can now use the calendar component in any Angular view.

<div class="calendar-wrapper">
    <mat-calendar #calendar [(selected)]="selectedDate">
    </mat-calendar>
</div>

We can then access this component in the typescript file through the ViewChild property decorator, as shown below:

  @ViewChild('calendar') calendar: MatCalendar<Moment>;
  selectedDate: Moment;

Calendar Events

As there is no documentation for this component, I had to figure things out myself. It appears the calendar component has two events that we can tap into: monthSelected and selectedChange.

monthSelected

As the name suggests this event fires when the user changes the currently displayed month.

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

monthSelected(date) {
  alert(`Selected: ${date}`);
}

However, the monthSelected event is unfortunately not fired for the arrow buttons on the top right hand corner. If you need to access these button clicks, I recommend using the Renderer2 service for wiring-up event handlers.

ngAfterViewInit() {
  const buttons = document.querySelectorAll('.mat-calendar-previous-button, .mat-calendar-next-button');

  if (buttons) {
    Array.from(buttons).forEach(button => {
      this.renderer.listen(button, 'click', () => {
        alert('Arrow buttons clicked');
      });
    });
  }
}

selectedChange

This event fires when there is a change in date selection. Newly selected date is passed into the event handler as an argument.

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

dateChanged(date) {
  alert(`Selected: ${date}`);
}

Creating a Reusable Calendar Component

Since we’re likely to add custom functionality around the calendar component, it makes a lot of sense to create a reusable component for it.

Generate a new component, call it Calendar:

ng generate component Calendar

Place the mat-calendar component in the HTML template:

// calendar.component.html

<div class="calendar-wrapper">
  <button mat-button (click)="prevDay()">Prev</button>
  <button mat-button (click)="today()">Today</button>
  <button mat-button (click)="nextDay()">Next</button>

  <mat-calendar #calendar (monthSelected)="monthSelected($event)" [(selected)]="selectedDate" (selectedChange)="dateChanged()">
  </mat-calendar>
</div>

I have added buttons above the calendar to demonstrate two-way binding. These buttons will set the current date of the calendar to previous day, today or next day.

Let’s also add some styling so that the calendar does not take up the full width of the page:

// calendar.component.css

.calendar-wrapper{
    width: 300px;
    text-align: center;
}

Implement the calendar in the component as shown below.

// calendar.component.ts

import { Component, Output, EventEmitter, ViewChild, Renderer2, AfterViewInit } from '@angular/core';
import { Moment } from 'moment';
import * as moment from 'moment';
import { MatCalendar } from '@angular/material';

@Component({
  selector: 'app-calendar',
  templateUrl: './calendar.component.html',
  styleUrls: ['./calendar.component.css']
})
export class CalendarComponent implements AfterViewInit {

  @Output()
  dateSelected: EventEmitter<Moment> = new EventEmitter();

  @Output()
  selectedDate = moment();

  @ViewChild('calendar')
  calendar: MatCalendar<Moment>;

  constructor(private renderer: Renderer2) { }

  ngAfterViewInit() {
    const buttons = document.querySelectorAll('.mat-calendar-previous-button, .mat-calendar-next-button');

    if (buttons) {
      Array.from(buttons).forEach(button => {
        this.renderer.listen(button, 'click', () => {
          console.log('Arrow buttons clicked');
        });
      });
    }
  }

  monthSelected(date: Moment) {
    console.log('month changed');
  }

  dateChanged() {
    this.calendar.activeDate = this.selectedDate;
    this.dateSelected.emit(this.selectedDate);
  }

  prevDay() {
    const prevMoment = moment(this.selectedDate).add(-1, 'days');
    this.selectedDate = prevMoment;
    this.dateChanged();
  }

  today() {
    this.selectedDate = moment();
    this.dateChanged();
  }

  nextDay() {
    const nextMoment = moment(this.selectedDate).add(1, 'days');
    this.selectedDate = nextMoment;
    this.dateChanged();
  }
}

The component is pretty simple but if there’s anything you don’t understand, please let me know.

Here is how to use the calendar component:

// app.component.html

<app-calendar #myCalendar (dateSelected)="dateSelected($event)">
</app-calendar>
// app.component.ts

import { Component, ViewChild } from '@angular/core';
import { CalendarComponent } from './calendar/calendar.component';
import { Moment } from 'moment';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  @ViewChild('myCalendar')
  myCalendar: CalendarComponent;

  dateSelected(value: Moment) {
    alert(value);
  }
}

Summary

In this post, we got our hands dirty with the calendar component of Angular Material library. I have been using this in a real-world application for quite some time and haven’t had any issues yet. While the component is very helpful, I am not sure why the documentation does not mention it. If you know the answer, drop a comment below!

Umut Esen

Umut is a certified Microsoft Certified Solutions Developer and has an MSc in Computer Science. He is currently working as a senior software developer for Royal London. He is the primary author and the founder of onthecode.

This Post Has 7 Comments

  1. A great article! Thank you!

  2. Great article Umut. Thanks for sharing. Any idea how to set min / max selection date and disable calendar?

    1. Umut Esen

      I would consider creating a wrapper component, which would be used as a generic calendar. This wrapper component would accept min/max date values through input parameters. All you would have to do then is to perform min/max validation inside onDateChanged event.

  3. It’s very helpful @Umut Esen … I couldn’t find a way to show multi-month view. Can you please let me know the solution for that.

  4. Thank you for the article. But the problem with this calendar is it doesn’t have two-way data bindings. If you change selectedDate value in App component after the calendar was rendered already – you won’t see any changes in the calendar. Unfortunately, I didn’t find a way how to fix it.

    1. Umut Esen

      Hi Iar, thank you for your comment. I have updated the post to demonstrate two-way binding. Hope this helps.

Leave a Reply

Close Menu

Free Template

Get your Angular Material application template to kick-start your next project.