What is the best way to implement an EventHandler class using TypeScript?

I am in the process of migrating my project from JavaScript to TypeScript and encountering an issue with transitioning a class for managing events.

To prevent duplicate option descriptions for adding/removing event listeners, we utilize a wrapper like this:

constructor() {
  this.windowResizeHandler = new MyEventHandler(
    target: window,
    event: 'resize',
    handler: e => this.handleResize_(e),
    options: {passive: true, capturing: true},
  );
} 

connectedCallback() {
  this.windowResizeHandler.add();
}

disconnectedCallback() {
  this.windowResizeHandler.remove();
}

Currently, I am unsure how to implement this in TypeScript without losing type information about events. For instance:

document.createElement('button').addEventListener('click', e => {
  // Here e is MouseEvent.
});

However, if I structure my wrapper as follows:

interface EventHandlerParams {
  readonly target: EventTarget;
  readonly event: Event;
  readonly listener: (e: Event) => void;
  readonly params: AddEventListenerOptions;
}

export class EventHandler {
  public constructor(params: EventHandlerParams) {}
}

Then I lose typings:

new MyEventHandler(
  target: document.createElement('button'),
  event: 'click',
  handler: e => { /* Here e is just Event not MouseEvent */ },
  options: {passive: true, capturing: true},
);

Is there a way for me to utilize event typings from lib.dom.d.ts?

Answer ā„–1

Within the lib.dom.ts file, you will find a section named WindowEventMap which contains mappings for all event names and their corresponding argument types.

For instance, consider the following code snippet:

interface EventHandlerParams<T extends keyof WindowEventMap> {
    readonly target: EventTarget;
    readonly event: T;
    readonly options: AddEventListenerOptions;
    readonly listener: (e: WindowEventMap[T]) => void
}

export class EventHandler<T extends keyof WindowEventMap> {
    public constructor(params: EventHandlerParams<T>) { }
}


new EventHandler({
    target: document.createElement('button'),
    event: 'click',
    options: { passive: true },
    listener: e => { e.x /* e is MouseEvent */ }
});

The EventHandlerParams now utilizes generics to capture the event name as the type parameter T. Additionally, we have made the EventHandler class generic, with its type determined by the parameters passed to it. With the value of T (which holds the string literal type for the event), we can access the actual parameter type from WindowEventMap and utilize it in our listener signature.

Please note: Prior to version 3.0, there may have been issues with the inference of arguments for the listener, potentially defaulting them to any. Should you encounter this problem, feel free to reach out so I can provide the pre-3.0 version.

Answer ā„–2

@titian-cernicova-dragomir Here is a potential solution that I have been considering, although it currently does not function as intended:

interface Mapping {
  [Window]: WindowEventMap;
  [HTMLElement]: HTMLElementEventMap;
}

interface EventHandlerParams<TTarget extends keyof Mapping,
                             TEventName extends keyof Mapping[TTarget],
                             TEvent extends Mapping[TTarget][TEventName]> {
  readonly event: TEventName;
  readonly listener: (event: TEvent) => void;
  readonly params?: AddEventListenerOptions;
  readonly target: TTarget;
}


export class EventHandler<TTarget extends keyof Mapping,
                          TEventName extends keyof Mapping[TTarget],
                          TEvent extends Mapping[TTarget][TEventName]> {
  public constructor(params: EventHandlerParams<TTarget, TEventName, TEvent>) {}
}

Unfortunately, this approach is not feasible due to the limitations in using types as interface properties and a lack of alternative options for enforcing constraints on TTarget.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Oops! Property 'month' cannot be set on undefined value due to a TypeError

Despite not receiving any errors from Visual Studio Code, Iā€™m encountering an error in Chrome's console. Below is the code snippet from my interfaces.ts file: export interface Data1{ month: string; employeeName: string; date: string; employmentSta ...

Issue: Incompatibility between React and TypeScript leading to an error message - "No

When I try to map through an array in my code, I encounter a significant error as shown below: // Home.tsx render() { const { inputs, outputs, expectedOutputs } = this.state; return ( <ContentContainer> {inputs.map((inpu ...

Ways to mock a static method within an abstract class in TypeScript

Having a difficult time testing the function Client.read.pk(string).sk(string). This class was created to simplify working with dynamoDB's sdk, but I'm struggling to properly unit test this method. Any help or guidance would be greatly appreciate ...

Encountering difficulty invoking a component method from d3's call() function

My current setup involves using D3 to drag and drop links in the following manner: .call(d3.drag() .on("start", linkDragStart) .on("drag", linkDragging) .on("end", linkDragEnd)); Recently, I decided to extract this functionality into a separate met ...

Problem encountered in a simple Jest unit test - Unexpected identifier: _Object$defineProperty from babel-runtime

Struggling with a basic initial test in enzyme and Jest during unit testing. The "renders without crashing" test is failing, as depicted here: https://i.stack.imgur.com/5LvSG.png Tried various solutions like: "exclude": "/node_modules/" in tsconfig "t ...

Cannot display data in template

After successfully retrieving JSON data, I am facing trouble displaying the value in my template. It seems that something went wrong with the way I am trying to output it compared to others. My function looks like this, getUserInfo() { var service ...

A TypeScript function that returns the ReturnType of a specific callback function

Is it possible to define an annotation for a function that accepts a callback, and have the function return type determined by the callback's return type? // Suppose the callback takes a number as argument function processCallback(cb: (arg:number) =&g ...

Using axiosjs to send FormData from a Node.js environment

I am facing an issue with making the post request correctly using Flightaware's API, which requires form data. Since Node does not support form data, I decided to import form-data from this link. Here is how my code looks like with axios. import { Fl ...

What is the best way to modify onClick events for elements in Ionic v4 with React?

Is there a way for me to interact with a button or IonItemSliding in order to alter the text or color of an element? <IonItemOptions side="start"> <IonItemOption color="success">Yes</I ...

Dealing with missing image sources in Angular 6 by catching errors and attempting to reset the source

When a user adds a blob to my list, sometimes the newly added image is still uploading when the list is refreshed. In this case, I catch the error and see the function starting at the right time: <img src="https://MyUrl/thumbnails/{{ blob.name }}" widt ...

What is the most effective way to determine the data type of a variable?

My search skills may have failed me in finding the answer to this question, so any guidance towards relevant documentation would be appreciated! I am currently working on enabling strict type checking in an existing TypeScript project. One issue I'v ...

React doesn't have file upload configured to update the state

I am working on integrating a file upload button that sends data to an API. To ensure only the button triggers the upload dialog and not the input field, I have set it up this way. Issue: The File is not being saved to state, preventing me from using a ...

Can you guide me on implementing AWS SDK interfaces in TypeScript?

Attempting to create an SES TypeScript client using AWS definitions file downloaded from this link My approach so far: /// <reference path="../typings/aws-sdk.d.ts" /> var AWS = require('aws-sdk'); var ses:SES = new AWS.SES(); The error ...

Modify a particular attribute in an array of objects

I am currently working on an Angular project and dealing with the following array object: { "DATA": [ { "CUSTOM1": [ { "value": "Item1", ...

Encountering the error message "Uncaught Promise (SyntaxError): Unexpected end of JSON input"

Below is the code snippet I am using: const userIds: string[] = [ // Squall '226618912320520192', // Tofu '249855890381996032', // Alex '343201768668266496', // Jeremy '75468123623614066 ...

Creating a fresh type in Typescript based on object keys that are already defined within an interface

Here is the scenario I am currently dealing with: interface ListField { code: number; message: string; } interface List { [key: string]: ListField; } export const allCodes: List = { FIRST: { code: 1, message: 'message 1', }, ...

When trying to access a specific property of an object in Typescript using a key that is defined as a subset of

UPDATE: Take a look at the revised solution below, inspired by @GarlefWegart's input. I've been exploring the creation of generic typings for dynamic GraphQL query outcomes (mostly for fun, as I suspect similar solutions already exist). I' ...

Tips for effectively utilizing Mongoose models within Next.js

Currently, I am in the process of developing a Next.js application using TypeScript and MongoDB/Mongoose. Lately, I encountered an issue related to Mongoose models where they were attempting to overwrite the Model every time it was utilized. Here is the c ...

Pass values between functions in Typescript

Currently, I have been working on a project using both Node JS and Typescript, where my main challenge lies in sharing variables between different classes within the same file. The class from which I need to access the max variable is: export class co ...

Using an array of functions in Typescript: A simple guide

The code below shows that onResizeWindowHandles is currently of type any, but it should be an array of functions: export default class PageLayoutManager { private $Window: JQuery<Window>; private onResizeWindowHandlers: any; constructor () { ...