DEV Community

Valentino Gagliardi
Valentino Gagliardi

Posted on • Edited on • Originally published at valentinog.com

TypeScript, event handlers in the DOM, and the this keyword

In this quick post you'll learn how to make TypeScript play well with the infamous this keyword when working with event handlers in the DOM.

What is this in JavaScript?

this in JavaScript is a magic keyword for: “whichever object a given function runs in”. Consider the following object and its nested function:

const person = {
  name: "Jule",
  printName: function() {
    console.log(this.name);
  }
};
Enter fullscreen mode Exit fullscreen mode

When I call person.printName(), this will point to the person object. this is everywhere in JavaScript and depending on the skill level developers simply decide to avoid it in fear of errors or embrace it 100%. I’m on the second group.

this is also convenient when working with event handlers in the DOM and you’ll see why in a minute.

What are event handlers in JavaScript?

The Document Object Model is a convenient representation of every element in an HTML page. Browsers keep this structure in memory and expose a lot of methods for interacting with the DOM.

HTML elements in the DOM are not static. They are connected to a primordial object named EventTarget which lends them three methods:

  • addEventListener
  • removeEventListener
  • dispatchEvent

Whenever an HTML element is clicked, the most simple case, an event is dispatched. Developers can intercept these events (JavaScript engines are event-driven) with an event listener.

Event listeners in the DOM have access to this because the function runs in the context object who fired up the event (an HTML element most of the times). Consider the following snippet:

const button = document.querySelector("button");
button.addEventListener("click", handleClick);

function handleClick() {
    console.log("Clicked!");
    this.removeEventListener("click", handleClick);
}
Enter fullscreen mode Exit fullscreen mode

Here removeEventListener is called on the HTML button who triggered the click event. Now let’s see what happens when we convert this code to TypeScript.

TypeScript and this

When converting our code to TypeScript the IDE and the compiler will complain with two errors:

error TS2531: Object is possibly 'null'.
error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.
Enter fullscreen mode Exit fullscreen mode

We can get rid of the first error with optional chaining, landed in TypeScript > 3.7:

const button = document.querySelector("button");
// optional chaining
button?.addEventListener("click", handleClick);

function handleClick() {
    console.log("Clicked!");
    this.removeEventListener("click", handleClick);
}
Enter fullscreen mode Exit fullscreen mode

For the second error to go away instead, this must appear as the first parameter in the handler signature, with the appropriate type annotation. HTMLElement is enough in this case:

const button = document.querySelector("button");
button?.addEventListener("click", handleClick);

function handleClick(this: HTMLElement) {
    console.log("Clicked!");
    this.removeEventListener("click", handleClick);
}

Enter fullscreen mode Exit fullscreen mode

You might have guessed how the “trick” is applicable to any function dealing with this, not necessarily an event handler (don’t mind any here, it’s bad):

function aGenericFunction(this: any, key: string) {
  return this.doStuff(key);
}

const aFictionalObject = {
  first: "a",
  second: "b",
  doStuff: function(str: string) {
    return `${this.first} ${str}`;
  }
};

aGenericFunction.call(aFictionalObject, "appendMe");
Enter fullscreen mode Exit fullscreen mode

Originally published on my blog

Top comments (1)

Collapse
 
vcfvct profile image
leon

The invoke statement should be
aGenericFunction.apply(aFictionalObject, ['appendMe']);
right?