8

I'm using vanilla JavaScript with TypeScript as pre-processor in combination with JSDoc.
That pretty much works flawlessly, especially in the backend (when using in NodeJS, for instance).

However, when I use it with DOM objects, things get a bit tricky.

For example: Say I have an HTML Input field, I catch the input event and want to access the input's value with e.target.value:

/**
 * Log data on input
 *
 * @param {Event} e
 */
let handleEvent = function(e){
    console.log(e.target.value);
};

document.getElementById("my-input").addEventListener("input", handleEvent);

This results in a TS warning:

Property 'value' does not exist on type 'EventTarget'

As seen here:

TypeScript Warning

Now my question is, what's the correct @param annotation?

So far I've tried Event, InputEvent and HTMLInputElement.

I don't want to use Type-Assertion. Instead I'd like to know how to specify it in the functions annotations directly, so I do not have to set @type for each and every occurrence of e.target.value individually as suggested here.

NullDev
  • 6,739
  • 4
  • 30
  • 54

1 Answers1

13

You can write your @param annotation like that:

/**
 * Log data on input
 *
 * @param {Event & { target: HTMLInputElement }} e
 */
let handleEvent = function(e){
    console.log(e.target.value);
};

Then you get full support for the correct types:

1

The reason why this works, is that you're basically "extending" the Event class by the new target property - which already exists, so it gets overridden with our new type.

The ampersand per se is not a jsdoc operator, but one from TypeScript. So this only works out if you keep using TypeScript as your preprocessor. See this for a more detailed explanation.

CreepSore
  • 406
  • 5
  • 8
  • Sadly not a lot less typing¹ than the type assertion. This approach is reasonable, but it has the feeling of a workaround. Still, if `Event`/`InputEvent` doesn't have a generic type parameter (and it seems not to), I'm not sure what else you'd do. ¹ *"typing" in the sense of pressing keys on a keyboard* – T.J. Crowder Feb 15 '21 at 13:26
  • @T.J.Crowder Imo it actually is less typing if you use `e` in the function body a lot. Doesn't feel like a workaround to me since I explicitly state the type of the parameter. The only "workaround" I could think of, would be accessing `this` instead of `e`. Nonetheless, I'm pretty happy with this solution :) – NullDev Feb 15 '21 at 13:37
  • 1
    @NullDev - You'd grab `target` to a local, once. `var target = /** @type {HTMLInputElement} */ (e.target);` But this `&` solution is **very** nearly the same as using a generic type parameter (actually, now I think about it, I'm not sure what the type parameter would offer other than a bit more conciseness), so seems pretty good. :-) – T.J. Crowder Feb 15 '21 at 13:40
  • 3
    As you mentioned, it just workd with TypeScript, not pure JS only with JSDoc. In this case the trick works on the function, but not when calling it in `addEventListener( handleEvent )`. Let's assume we want to use target and originalTarget. JSDoc outputs this error on the parameter: **Argument of type 'MouseEvent' is not assignable to parameter of type 'MouseEvent & { originalTarget: HTMLInputElement; target: HTMLInputElement; }'. Property 'originalTarget' is missing in type 'MouseEvent' but required in type '{ originalTarget: HTMLInputElement; target: HTMLInputElement; }'.ts(2345)** – fips Dec 28 '21 at 08:34