Angular: How to build a full screen calendar like Outlook
Ricky Stam
Posted on May 7, 2020
In an Angular project a while back I needed to display a full screen calendar like the one in outlook. So as a good lazy developer I start looking on the web for a NPM package that could do the job.
To my surprise I didn't find anything that could cover my needs 100% so I went on and built one!
This is the end result:
P.S.: Please be kind with me, HTML and CSS are not my strong suit.
Here is the coding story of how I did:
1st let's have our Angular component
This is our starting point an Angular component and an Array that will hold the days that our calendar will display
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
public calendar: CalendarDay[] = [];
}
2nd let's see how the CalendarDay class looks like
export class CalendarDay {
public date: Date;
public title: string;
public isPastDate: boolean;
public isToday: boolean;
constructor(d: Date) {
this.date = d;
this.isPastDate = d.setHours(0, 0, 0, 0) < new Date().setHours(0, 0, 0, 0);
this.isToday = d.setHours(0, 0, 0, 0) == new Date().setHours(0, 0, 0, 0);
}
}
Let explain the constructor a little bit.
this.isPastDate = d.setHours(0, 0, 0, 0) < new Date().setHours(0, 0, 0, 0);
this.isToday = d.setHours(0, 0, 0, 0) == new Date().setHours(0, 0, 0, 0);
I set property isPastDate so my calendar knows how to display or disable past dates and isToday property so UI knows how to draw the today's date.
The reason I use .setHours(0,0,0,0) is cause I want to be sure I'm comparing the beginning of the day and hours don't matter.
3rd let's populate our calendar with the needed days
I have comments in my code that explain the logic.
ngOnInit(): void {
// here we initialize the calendar
this.generateCalendarDays();
}
private generateCalendarDays(): void {
// we reset our calendar every time
this.calendar = [];
// we set the date
let day: Date = new Date();
// here we find the first day that our calendar will start from
// it would be the last Monday of the previous month
let startingDateOfCalendar = this.getStartDateForCalendar(day);
// dateToAdd is an intermediate variable that will get increased
// in the following for loop
let dateToAdd = startingDateOfCalendar;
// ok since we have our starting date then we get the next 41 days
// that we need to add in our calendar array
// 41 cause our calendar will show 6 weeks and MATH say that
// 6 weeks * 7 days = 42!!
for (var i = 0; i < 42; i++) {
this.calendar.push(new CalendarDay(new Date(dateToAdd)));
dateToAdd = new Date(dateToAdd.setDate(dateToAdd.getDate() + 1));
}
}
private getStartDateForCalendar(selectedDate: Date){
// for the day we selected let's get the previous month last day
let lastDayOfPreviousMonth = new Date(selectedDate.setDate(0));
// start by setting the starting date of the calendar same as the last day of previous month
let startingDateOfCalendar: Date = lastDayOfPreviousMonth;
// but since we actually want to find the last Monday of previous month
// we will start going back in days intil we encounter our last Monday of previous month
if (startingDateOfCalendar.getDay() != 1) {
do {
startingDateOfCalendar = new Date(startingDateOfCalendar.setDate(startingDateOfCalendar.getDate() - 1));
} while (startingDateOfCalendar.getDay() != 1);
}
return startingDateOfCalendar;
}
4th let's add some HTML and CSS to actually start displaying our calendar
In the HTML you will see that I'm using a pipe named chunk I'll explain the use of it and the code in a bit
<table class='calendar-table' *ngIf="calendar">
<thead>
<tr>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
<th>Sunday</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of calendar | chunk: 7; let i = index">
<td class="calendar-day" [ngClass]="{'past-date': c.isPastDate, 'today': c.isToday}" *ngFor="let c of row; let j = index">
<div class="calendar-day-header" [ngClass]="{'blue-date': c.isToday}"><strong>{{c.date.getDate()}}</strong> <strong *ngIf="c.isToday || (i==0 && j==0) || (c.date.getDate() == 1)"> {{monthNames[c.date.getMonth()]}}</strong></div>
</td>
</tr>
</tbody>
</table>
.calendar-table {
border-collapse: collapse;
width: 100%;
max-width: 100%;
margin-bottom: 1rem;
border: 1px solid #dee2e6;
background-color: #fff;
}
.calendar-table thead th {
vertical-align: bottom;
border-bottom: 2px solid #dee2e6;
width: 14.2%;
}
.calendar-table td, .calendar-table th {
border: 1px solid #dee2e6;
}
.calendar-table td, .calendar-table th {
padding: .75rem;
vertical-align: top;
border-top: 1px solid #dee2e6;
}
.calendar-day {
height: 12vh;
max-height: 12vh;
cursor: pointer;
}
.calendar-items-wrapper {
margin-left: -10px;
margin-right: -10px;
overflow-y: auto;
max-height: calc(100% - 20px);
}
.calendar-day.past-date {
background-color: rgb(248, 248, 248);
}
.calendar-day:hover {
background-color: rgb(248, 248, 248);
}
.blue-date {
color: rgb(16, 110, 190);
}
5th now it's time to explain and show the code for the chunk pipe
Since our calendar array has 42 elements but we want to show 7 elements in each row the chunk pipe will create an array with 6 arrays inside one array for each week.
@Pipe({
name: 'chunk'
})
export class ChunkPipe implements PipeTransform {
transform(calendarDaysArray: any, chunkSize: number): any {
let calendarDays = [];
let weekDays = [];
calendarDaysArray.map((day,index) => {
weekDays.push(day);
// here we need to use ++ in front of the variable else index increase
//will happen after the evaluation but we need it to happen BEFORE
if (++index % chunkSize === 0) {
calendarDays.push(weekDays);
weekDays = [];
}
});
return calendarDays;
}
}
This post was written with love ❤️
Posted on May 7, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.