Angular: How to build a full screen calendar like Outlook

rickystam

Ricky Stam

Posted on May 7, 2020

Angular: How to build a full screen calendar like Outlook

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 ❤️

💖 💪 🙅 🚩
rickystam
Ricky Stam

Posted on May 7, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related