How to build a calendar in Angular
Building a custom calendar in Angular is a common requirement for applications involving scheduling, booking, or event management.
With over 25 years of experience in software development and as the creator of CoreUI, I’ve designed and implemented numerous calendar systems ranging from simple date pickers to complex resource schedulers.
The most efficient and modern approach involves leveraging TypeScript’s Date object to calculate month structures and using CSS Grid for a responsive, accessible layout.
While building from scratch is great for learning, for enterprise-grade applications, using a battle-tested library like CoreUI can save weeks of development time.
Calculate an array of date objects for the current month, including padding for leading empty days, and render them using an *ngFor loop in a 7-column CSS Grid.
import { Component, OnInit } from '@angular/core'
@Component({
selector: 'app-calendar',
templateUrl: './calendar.component.html',
styleUrls: ['./calendar.component.css']
})
export class CalendarComponent implements OnInit {
public days: Date[] = []
public currentMonth: number = new Date().getMonth()
public currentYear: number = new Date().getFullYear()
public viewDate: Date = new Date()
ngOnInit(): void {
this.generateCalendar(this.currentYear, this.currentMonth)
}
generateCalendar(year: number, month: number): void {
const firstDayOfMonth = new Date(year, month, 1)
const lastDayOfMonth = new Date(year, month + 1, 0)
// Day of the week for the first day (0-6)
const startDay = firstDayOfMonth.getDay()
const totalDays = lastDayOfMonth.getDate()
this.days = []
// Calculate padding for the beginning of the month
for (let i = startDay; i > 0; i--) {
this.days.push(new Date(year, month, 1 - i))
}
// Add days of the current month
for (let i = 1; i <= totalDays; i++) {
this.days.push(new Date(year, month, i))
}
// Add padding for the end of the month to complete the last row
const remainingDays = 42 - this.days.length // 6 weeks * 7 days
for (let i = 1; i <= remainingDays; i++) {
this.days.push(new Date(year, month + 1, i))
}
}
}
This first code section handles the core logic of date calculation. We use the Date constructor to find the first and last days of the month. By setting the day parameter to 0 in new Date(year, month + 1, 0), we effectively get the last day of the current month. The padding logic ensures the calendar always displays a consistent 6-week grid (42 days), which prevents the UI from “jumping” when navigating between months of different lengths.
Implementing the Template with CSS Grid
Once the data structure is ready, the next step is to render the calendar in the component’s template using Angular’s structural directives.
<div class="calendar-wrapper">
<div class="calendar-header">
<button (click)="changeMonth(-1)">Prev</button>
<h2>{{ viewDate | date: 'MMMM yyyy' }}</h2>
<button (click)="changeMonth(1)">Next</button>
</div>
<div class="calendar-grid">
<div class="weekday" *ngFor="let day of ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']">
{{ day }}
</div>
<div
class="calendar-cell"
*ngFor="let date of days"
[class.today]="isToday(date)"
[class.inactive]="date.getMonth() !== currentMonth"
>
<div class="cell-header">
<span class="day-number">{{ date.getDate() }}</span>
</div>
<div class="cell-content">
<!-- Event placeholders can go here -->
</div>
</div>
</div>
</div>
The template uses a combination of standard HTML and Angular’s *ngFor directive. The calendar-grid class is designed to be styled with display: grid and grid-template-columns: repeat(7, 1fr). This creates the classic seven-column layout automatically. We also use property binding [class.today] to highlight the current day and [class.inactive] to dim days belonging to adjacent months.
Adding Navigation Logic
To make the calendar interactive, we need to add methods to navigate between months and update the component state.
changeMonth(delta: number): void {
this.currentMonth += delta
if (this.currentMonth < 0) {
this.currentMonth = 11
this.currentYear--
} else if (this.currentMonth > 11) {
this.currentMonth = 0
this.currentYear++
}
this.viewDate = new Date(this.currentYear, this.currentMonth, 1)
this.generateCalendar(this.currentYear, this.currentMonth)
}
isToday(date: Date): boolean {
const today = new Date()
return date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear()
}
The changeMonth method handles the rollover between years (e.g., going from December to January). After updating the month and year, it calls generateCalendar to refresh the days array. The isToday helper function is critical for providing immediate visual feedback to the user. This logic ensures that the state is always synchronized with the view.
Styling the Grid for Responsiveness
Without proper CSS, the calendar will just be a vertical list. Here is the styling required to create a professional, responsive grid.
.calendar-wrapper {
border: 1px solid #dee2e6;
border-radius: 0.25rem;
overflow: hidden;
background: #fff;
}
.calendar-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
background: #dee2e6;
gap: 1px;
}
.weekday {
background: #f8f9fa;
padding: 0.5rem;
text-align: center;
font-weight: 700;
font-size: 0.875rem;
}
.calendar-cell {
background: #fff;
min-height: 100px;
padding: 0.5rem;
transition: background 0.2s;
}
.calendar-cell.today {
background: #e7f3ff;
}
.calendar-cell.inactive {
color: #adb5bd;
background: #fcfcfc;
}
.day-number {
font-size: 0.9rem;
}
This CSS defines a responsive grid layout. The gap: 1px on the calendar-grid combined with the background color of the grid itself creates the cell borders without needing complex border-collapse logic. Each calendar-cell has a min-height to allow space for content, such as event markers or task lists.
Integrating CoreUI PRO for Advanced Features
For production-grade applications, building a calendar from scratch can be time-consuming when you need features like event drag-and-drop, date range selection, or internationalization. The CoreUI Calendar is a PRO component available in the @coreui/angular-pro package.
<!-- Example of using CoreUI PRO Calendar Component -->
<c-calendar
[events]="calendarEvents"
[view]="'month'"
(dateClick)="handleDateClick($event)"
(eventClick)="handleEventClick($event)"
>
</c-calendar>
By leveraging the CoreUI Angular Calendar component, you gain access to a professionally designed UI that follows all Bootstrap standards. It integrates seamlessly with the rest of our Angular Dashboard Template, providing a consistent look and feel. This is the preferred method for enterprise projects where reliability and performance are paramount.
Best Practice Note:
When building calendars, always handle date calculations carefully. If your application spans multiple time zones, consider performing all calculations in UTC or using a library like date-fns to avoid off-by-one errors during Daylight Saving Time transitions. This is the same philosophy we follow in CoreUI to ensure cross-browser consistency. If you’re just starting, you might also want to learn how to get the current date in JavaScript or how to format a date as YYYY-MM-DD to master the underlying primitives used in this guide.



