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 ❤️
Top comments (20)
Thanks for your solution
Good job!
Thank you :)
This piece of code here is so cool
weekDays.push(days)
if (++index % chunkSize === 0) {
calendarDays.push(weekDays);
weekDays = [];
}
Thank you herrigor, I hope you find it useful :)
Thank you for your post it was very helpful. I added a method to determine the exact number of days for the number day in the calendar instead of 42 in generateCalendarDays and thought I would pass it on:
/**
Looks good man
Thank you Don, glad you like it!
This is amazing 🎉🎉
You did great Ricky!
Many thanks :)
Very nice
Hi Ricky. It's awesome. Just wanted to know why we use map instead of foreach in pipe. Am new in angular
Hi surajKurade!
For this example it wouldn't make any difference to be honest. Map and ForEach both iterate the array and apply the given function, their difference is that ForEach doesn't return a value just iterates the array and executes the given function on the elements but map will return a new array with the modified array.
Usually people are using map when they want to achieve immutability (they don't want to modify their array but to return a modified copy)
Example:
Hope this helps!
Thanks Ricky 👍
Hi Ricky, Can we create room and meeting booking in your good calendar ?
Hi @pkhussain sure you can use it as you like :) Just keep in mind that this is just a POC and might have bugs, I've never tested it thoroughly, so use with causion!