Building an ES6 application with the help of TypeScript's DOM types
Write an ES6 app using the right tools.
In this article, I propose a way of writing relatively simple apps without using a fully-fledged framework. It is based on my experience during an interview process. I am not trying to persuade you to stop writing your code with the latest frameworks (please keep doing that!). I am just proposing an interesting way of deeply understanding how HTML and JavaScript work together with the DOM.
You might think that writing a simple standard JavaScript application can be easy, but it always depends on the requirements. You might find yourself facing a nightmare without the right tools.
When applying for jobs, it is often mandatory to deliver an assignment as part of the interview process. Companies that want to recruit smart people and really take care of their code quality usually include it to have a better understanding of what type of employee they are hiring.
I was in this position a couple of months ago when I was interviewing for a frontend position. I had an initial call with the recruiter, with the usual introduction of the company, and later they sent me "the assignment".
The task was not complicated, but there was a catch. In the notes, they clearly stated: " It would be preferable not to use any fully-fledged framework to help us understand your engineering decisions ".
Fair enough, I immediately knew I might not be able to finish it on time without the right tools, and this is the story of how TypeScript helped me through this journey.
Let's get to the point now, my assignment consisted of writing an application where the user is able to see the latest movies, using the TMDB service. In another view, I had to implement a "Search a movie" feature, which needed to show results as you typed. The requirements included writing code for a performant infinite scroll. You can see a demo of how the finished application works here.
I am not going to talk about the whole app, this is just to give you an idea of the things you can do with the approach I propose. For the sake of simplicity, I am going to focus on a simple part, the navigation to “Latest movies” view and the “Search movie” view. If you are curious, the complete assignment is my repo.
I wanted the resulting HTML to look like the plain HTML below.
This is not feasible to do without the help of the fancy frameworks, and I didn't have enough time to develop a framework of my own. And of course, writing a full framework just for an assignment is an overkill.
So I needed to add the nodes with JavaScript, using the plain document.createElement
method. I had to either remember by heart all the methods for the elements I was creating or spend a lot of time at Mozilla's MDN site. This is very error-prone, not to mention that I only found errors at runtime. Just imagine how much time I would have wasted, and time was ticking for me.
So I decided to use TypeScript to help me with that.
I started with something like this:
Let me tell you something about types for the DOM in TypeScript since they are awesome. Writing this code using this language is actually quite easy because of all the help the IDE can give you in terms of auto-completion.
This is how the document.createElement
is typed with TypeScript:
As you can see, the tagName
is of type K
, which can only be one of the possible keys of the HTMLElementTagNameMap
interface. So you get compile-time checks on the tag name you pass to the createElement
function.
Furthermore, the return type is HTMLElementTagNameMap[K]
.This means that when you type document.createElement('ul')
the actual type of the created element is HTMLUListElement
, which is great because it means you can have autocompletion on the possible properties of an Unordered List.
Using TypeScript like that makes the code easier to write, yes, but the result is not that easy to read.
For starters, it is difficult to figure out the resulting HTML structure from reading it. If I hadn't shown you the HTML I wanted at the beginning, it would be hard for you to picture it in your mind. This is mainly because the code doesn't reflect the hierarchy of the HTML.
Secondly, it uses a lot of hard-to-name variables. When writing it, I needed to come up with lots of variable names. That is why you see things like ulEl
, liTheaters
, liSearch
, etc, etc.
How could I solve this? The idea would be to have a function called, for example, createEl
that would receive the tag name, just like the standard document.createElement
, but have extra parameters to specify attributes, children and event handlers inline.
A much intuitive code should look like this now using createEl
You can clearly see the HTML hierarchy represented in the code and the problem about the variable names is gone since we don’t need to have temporary variables anymore.
Sure, the code is not as awesome as if I wrote it with a proper HTML or used any of the latest frameworks, but, it is really an improvement over the first one I showed you, and for me at this point that was good enough.
The code for the createEl
function looks like this:
Let's now talk about the limitations of this approach. This solution is obviously far from perfect since it is not as flexible as writing the code with Angular, React, Vue or whatever other frameworks you may be used to.
One restriction is that not all the attributes available in HTML are available for us in the "same way". For example, we can't use the attribute class
in our solution, instead, we are forced to use className
, since this is what the DOM API has becauseclass
is a reserved word in JavaScript.
Also, adding event handlers is a bit cumbersome and verbose.
More importantly, we are abusing the interfaces from HTMLElementTagNameMap[K]
. These interfaces are designed to map to the actual DOM object that would be created with document.createElement
and we are using them in the interest of having available some of their properties, to use them as HTML attributes. For example, in HTMLButtonElement
we have the property form
that is read-only, so we cannot really use it. If you were to pass it as an attribute to the createEl
function, it would be ignored.
I am not saying "stop using the modern frameworks", please don't, they are really awesome. What I want to emphasize that an exercise like this helps to profoundly understand how to manipulate the DOM.
If you find yourself in my shoes, just remember that an assignment like this is much easier using TypeScript. The margin for error is narrower thanks to the type system, and the IntelliSense from the IDE acts as implicit documentation that comes for free.
Thanks for reading! If you have any suggestions or ideas, please let me know.