Creating a “lite” Calendar in Angular with a dash of Vanilla JS
I understand that using vanilla JS within Angular to directly manipulate the DOM is generally frowned upon, but sometimes when working with a number of dynamically created elements that need referencing it’s the easiest way to go.
So, below you’ll see mostly vanilla JS with some TypeScript functions and scoped variables.
Creating Table Structure
The first thing we need to determine is how we want to structure the HTML of the page. I’m using HTML tables for this light-weight version because there’s less CSS and JS needed, but later will convert to CSS grid with just DIVs.
Every month’s table layout is different depending on the month and year, but first we need to set some defaults and variables.
// set the calendars year and month to todays date by defaultthis.today = new Date().getDate();
this.year = new Date().getFullYear();
this.month = new Date().getMonth();// get the first day of the month. this is an enumerated index, so 0 is Sunday and 6 is Saturday.this.firstDay = (new Date(year, month, 1)).getDay();// using a 0 param in the day slot of Date() gives you the last day automatically. fantastic.this.daysInMonth = new Date(year, month, 0).getDate();
Once we’ve set up all of the data we need. We have to build the calendar accordingly. I have this all within the function createTable() so that any time the month or year changes we can call it afterwards.
// need to set todaysMonth and todaysYear for later conditionalsconst todaysMonth = new Date().getMonth();
const todaysYear = new Date().getFullYear();const tbl = document.getElementById('calendar');// clear whatever table currently exists before creating the new onetbl.innerHTML = null;// let's start off on the first of the month and iterate throughlet date = 1;// the maximum number of rows on any calendar regardless of the number of days is 6. (Look at August 2020)for (let i = 0; i < 6; i++) {
const row = document.createElement('tr');for (let j = 0; j < 7; j++) {
// check if day exists in the first week if (i === 0 && j < this.firstDay) { const cell = document.createElement('td'); const cellText = document.createTextNode(''); cell.appendChild(cellText); row.appendChild(cell); } else if (date > this.daysInMonth) { // no element if it doesn't exist in that month break; } else { const cell = document.createElement('td'); // give each TD element an ID for easy reference cell.setAttribute('id', 'day-' + date); // add class 'past' to the TD so we can style if (this.month === todaysMonth && this.year === todaysYear){ if (date < this.today) { cell.classList.add('past'); } } if (this.month < todaysMonth) { cell.classList.add('past'); } cell.classList.add('day'); const span = document.createElement('span'); const cellText = document.createTextNode(date + ''); span.appendChild(cellText); cell.appendChild(span); row.appendChild(cell); date++; } }tbl.appendChild(row);
The above seems a touch daunting at first look, but let’s break down what we’re looking at here. I’m looping through the max number of rows in a month (6) and then the respective months number of days to:
- Determine where the day should display
- Give each cell an ID corresponding to the day
- Determine whether the day is in the “past”.
The fun thing about the createTable() function is that if we simply subtract or add to the scoped “this.month” variable we can run this function again and it works.
previousMonth() { this.month = this.month - 1; // if we go back to the previous year if (this.month === -1) { this.month = 11; this.year = this.year - 1; } this.firstDay = (new Date(this.year, this.month, 1)).getDay(); this.daysInMonth = new Date(this.year, this.month, 0).getDate(); this.createTable(); }
Now we have our layouts, but what about adding events?
The nice thing here is that since we have an ID on every single TD element we can create a listener on every TD element and then grab it’s corresponding ID. This is within the createTable() function so that every time a new calendar view is generated the listeners are set up.
const classname = document.getElementsByClassName('day');const addEditEvent = (e) => { // create a faux element on click and open a modal const td = document.getElementById(e.target.id) as HTMLElement; const div = document.createElement('div'); div.innerHTML = '<div class="new-event"></div>'; td.appendChild(div); // set the date clicked for saving the event later this.date.setDate(parseInt(e.target.firstChild.innerHTML, 10)); // open modal however you'd like and pass this.date } for (let i = 0; i < classname.length; i++) { classname[i].addEventListener('click', addEditEvent, false); }
Below you’ll see how the faux element is within the TD and the modal appears to the right of the date with the appropriate input fields. I’m doing some calculations to determine the position, but either way, notice how the date and time are already set respectively (it’s 10:50am here in Houston).

Our company’s API is very complex and flexible, so we won’t get into the data aspect of things, but you’ll notice on the image above we see some saved events already. Here’s how I loop through the array of events I get back from our API and display them in the correct table cells.
const tdArray = document.getElementsByClassName('event-info'); // first we remove everything from the existing table because we may have just changed months Array.prototype.forEach.call(tdArray, function(td) { td.parentNode.removeChild(td); }); // API Call here returns events this.events = response.events; // Ignore the getDateTime function; it simply returns the day for the event which was provided in Zulu time this.events.forEach((event) => { const day = getDateTime(event['dateTime'], 'day'); const td = document.getElementById('day-' + day) as HTMLElement; const div = document.createElement('div'); // set the event id to the id of the element. could also use an HTML5 attribute like [data-id]=1276 div.innerHTML = '' + event['description'] + ''; td.appendChild(div); });
Now that we have our events displaying in the view, we need to update our listener on click to determine if the user is creating a new event or trying to view/edit an existing one. You’ll notice that our function before was named “AddEditEvent” for this reason.
const addEditEvent = (e) => { if (e.target.className.includes('event-info')) { const eventId = e.target.id.replace('event-', ''); this.loadEvent(parseInt(eventId, 10)); } else { // what we did before with the create event function above } } loadEvent() { this.events.forEach((event) => { if (event.id === eventId) { // set the event data for the modal } }); }

In the final version above you’ll see that I added more functionality that allows for all day events and display of the time, but overall what I’ve shown in this article is about 60% of the overall code for a usable lite-weight calendar. Of course, the CSS I will leave to you.