Introduction
I am currently upskilling myself in TypeScript as part of the skills I need to land a Job as a Front End Developer. I started using Hitesh Choudhary's course on Youtube to understand how TypeScript works, Hitesh is a great teacher!, you should check out his Youtube channel to see what he does. In this article, I want to show you how I used TypeScript to rewrite the code for a Front End Mentor project I built with JavaScript, Front End Mentor is a great platform for building projects as a software developer, and not get stuck in tutorial hell. For those that are not familiar with TypeScript, let me give you a basic introduction to what it is
What is TypeScript?
TypeScript is a superset of JavaScript, meaning any valid JavaScript code is also valid TypeScript code, which makes it easy to adopt TypeScript incrementally in any existing JavaScript project. One of the most important features TypeScript brought into JavaScript is Type Checking. JavaScript is not strict with type checking, you might declare a variable to be a number, and later on change it to be a string somewhere else without any errors whatsoever (until you run the code of course). Here is an example
let num = 10;
num = "Hello";
console.log(num); // This is perfectly fine with JavaScript
If you have used languages like Java, C#, C++ etc, you know that these languages have Type Checking built into them. TypeScript introduced Type checking into JavaScript to reduce errors in our code (like the one above), and help us write code that is production ready. With TypeScript, you declare the types for your variables so TypeScript can warn you when you are attempting to do something wrong. There are other features TypeScript has, but type checking is the most important feature TypeScript provides, which makes it easier to write code in JavaScript. To get started using TypeScript, you will need to install it globally on your system with npm
npm install -g typescript
Then create a .ts file in your project folder e.g index.ts, and put this simple snippet
let num: number = 10
To run this code with the TypeScript compiler, open your terminal and run this command
tsc index.ts
This will convert the code from TypeScript to JavaScript!. Now that you have some basic understanding of what TypeScript is, and how to use it, let me show you how I used it to rewrite my FEM project
The Project
After learning some of the basics from Hitesh's course, I wanted to start putting them into practice straight away, so I was looking for some projects I did with JavaScript and found this Newsletter sign up project I did from Front End Mentor. Converting the code of this project wasn't easy, but it pushed me out of my comfort zone, and helped me learn a whole lot more about TypeScript which tutorials wouldn't have thought me (The best way of learning anything as a developer is by building projects). Here is the Github repo and Preview of the application if you like to check them out. Here is the HTML code of the application
<main>
<section id="newsletter" class="newsletter">
<picture class="newsletter__image">
<source
srcset="assets/images/illustration-sign-up-desktop.svg"
media="(min-width: 81.25em)"
/>
<img src="assets/images/illustration-sign-up-mobile.svg" alt="" />
</picture>
<div class="newsletter__content">
<h2>Stay updated!</h2>
<p>Join 60,000+ product managers receiving monthly updates on:</p>
<ul class="newsletter__features">
<li class="feature__item">
Product discovery and building what matters
</li>
<li class="feature__item">
Measuring to ensure updates are a success
</li>
<li class="feature__item">And much more!</li>
</ul>
<form method="POST" class="newsletter__form">
<label for="email">Email address</label>
<input
type="email"
name="email"
id="email"
class="newsletter__input"
placeholder="email@company.com"
/>
<span class="form-error"></span>
<button type="submit" class="subscribe__btn">
Subscribe to monthly newsletter
</button>
</form>
</div>
</section>
<section id="subscription-message" class="subscription__success d-none">
<div class="subscription__message">
<img
src="./assets/images/icon-success.svg"
class="subscription__icon"
alt=""
/>
<h3 class="subscription__heading">Thanks for subscribing!</h3>
<p class="subscription__content">
A confirmation email has been sent to
<span class="subscription__email"></span>. Please open it and click
the button inside to confirm your subscription.
</p>
</div>
<button id="dismiss" class="subscribe__btn">Dismiss message</button>
</section>
</main>
This is the JavaScript code
const newsletter = document.querySelector("#newsletter");
const newsletterForm = document.querySelector(".newsletter__form");
const subscriptionMessage = document.querySelector("#subscription-message");
const formInput = document.querySelector(".newsletter__input");
const formError = document.querySelector(".form-error");
const dismissBtn = document.querySelector("#dismiss");
const emailText = document.querySelector(".subscription__email");
const emailRegex = /^[a-zA-Z]+@[a-zA-Z]+\.[a-zA-Z]{2,4}$/;
newsletterForm.addEventListener("submit", (e) => {
e.preventDefault();
let status = true;
if (formInput.value == "") {
status = false;
formInput.classList.add("newsletter__input--error");
formError.innerHTML = "Email is required";
}
if (formInput.value != "" && formInput.value.match(emailRegex) === null) {
status = false;
formInput.classList.add("newsletter__input--error");
formError.innerHTML = "Invalid email address";
}
if (status) {
newsletter.classList.add("d-none");
subscriptionMessage.classList.remove("d-none");
emailText.innerHTML = formInput.value;
}
});
formInput.addEventListener("input", () => {
if (formInput.classList.contains("newsletter__input--error")) {
formInput.classList.remove("newsletter__input--error");
formError.innerHTML = "";
}
});
dismissBtn.addEventListener("click", () => {
newsletter.classList.remove("d-none");
subscriptionMessage.classList.add("d-none");
});
I won't explain the entire codebase, only the ones that had to do with me implementing TypeScript. The first thing I needed to do was define the types for some variables I declared above because when you select elements on the DOM, they have a default type of Element
, this is a generic type that does not have specific properties and methods for HTML elements like <form>, <button> etc. The newsletterForm
variable for example is a form element, if the type is not changed, we won't be able to access certain properties and methods available to form elements like e.preventDefault()
because TypeScript is not smart enough to know we are selecting a form element. One way to specify the types we want in TypeScript is by using a technique called Type Assertion.
What is Type Assertion?
Type Assertion tells TypeScript you are certain about what the type of an element or variable is when TypeScript is not sure. For example, TypeScript is not sure what newsletterForm
is, so we can use Type Assertion to let the compiler know that this variable is an HTML Form Element
const newsletterForm = document.querySelector(".newsletter__form") as HTMLFormElement;
The next thing I am doing further down the code is attaching an event listener to the form element
newsletterForm.addEventListener("submit", (e) => {});
I have told TypeScript that newsletterForm is a form element, but what if the element I assigned to the newsletterForm variable doesn't exist?, or the variable is not defined?, I will run into this problem
Uncaught TypeError: Cannot read properties of null
You must have seen this error in your console before, so how do you solve this issue?, by using what is called Optional Chaining. The optional chaining operator is used to safely access properties or methods of an object without running into unnecessary errors. If the object is not defined, the code is short circuited, and any code written after it will never execute. For example, this is how the optional chaining operator will be used with the newsletterForm variable.
newsletterForm?.addEventListener("submit", (e) => {});
We are now sure that even if newsletterForm returns null, the event listener code will never execute, which makes our code more secure. Let's now enter the code of the event listener
There is an issue with the if statement, because TypeScript is showing a red dashed line on the formInput
variable, why is that?, because it is not sure what formInput is, you can use optional chaining to solve this. The next problem we have is trying to access the value
property, because TypeScript is interpreting formInput to be of the type Element, and the Element type doesn't have a value property on it, so we also have to tell TypeScript that formInput is an HTMLInputElement
, not an Element.
const formInput = document.querySelector(".newsletter__input") as HTMLInputElement;
newsletterForm?.addEventListener("submit", (e) => {
e.preventDefault();
let status = true;
if (formInput?.value == "") {
...
}
});
We also need to fix the formError
variable because typeScript is complaining that it isn't sure what formError is. In this case, we first need to check if formError does not return null before changing the innerHTML value because you can't do assignment operations with optional chaining, why is that?, there is a chance the variable won't exist, so what is the point of assigning it a value?, makes sense to me.
newsletterForm?.addEventListener("submit", (e) => {
e.preventDefault();
let status = true;
if (formInput?.value == "") {
status = false;
formInput?.classList.add("newsletter__input--error");
if (formError) {
formError.innerHTML = "Email is required";
}
}
if (formInput?.value != "" && formInput?.value.match(emailRegex) === null) {
status = false;
if (formError) {
formInput?.classList.add("newsletter__input--error");
formError.innerHTML = "Invalid email address";
}
}
});
Next we go to the other event listener that listens for when the user types into the input box
formInput.addEventListener("input", () => {
if (formInput.classList.contains("newsletter__input--error")) {
formInput.classList.remove("newsletter__input--error");
formError.innerHTML = "";
}
});
This is the same issue we have already solved before by using the optional chaining operator, and checking if formError exists before setting the innerHTML value
formInput?.addEventListener("input", () => {
if (formInput.classList.contains("newsletter__input--error")) {
formInput.classList.remove("newsletter__input--error");
if (formError) {
formError.innerHTML = "";
}
}
});
I will also do the same thing for the dismiss button I added an event listener to
dismissBtn?.addEventListener("click", () => {
newsletter?.classList.remove("d-none");
subscriptionMessage?.classList.add("d-none");
});
Conclusion
Rebuilding this project with TypeScript has taught me about Type Assertion, optional chaining and how to declare types for HTML Elements, I hope you also got to learn something from my experience in rebuilding this Front End Mentor project with TypeScript. If there is anything you will like to suggest that will help improve this article, I am opened to feedback. You can follow me on X and Linkedin.
Top comments (0)