0

I have a function that takes an HTMLElement and an object; the object's keys are event types and the values are functions:

function addListeners(el: HTMLElement, listeners: { [key in keyof HTMLElementEventMap]: Function }) {
  if (listeners) {
    for (const [key, value] of Object.entries(listeners)) {
      el.addEventListener(key, value);
    }
  }
}

However, Typescript raises these two errors:

No overload matches this call.
  Overload 1 of 2, '(type: keyof HTMLElementEventMap, listener: (this: HTMLElement, ev: Event | ClipboardEvent | UIEvent | AnimationEvent | MouseEvent | ... 13 more ... | WheelEvent) => any, options?: boolean | ... 1 more ... | undefined): void', gave the following error.
    Argument of type 'string' is not assignable to parameter of type 'keyof HTMLElementEventMap'.
  Overload 2 of 2, '(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | undefined): void', gave the following error.
    Argument of type 'Function' is not assignable to parameter of type 'EventListenerOrEventListenerObject'.
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Edgar Derby
  • 2,543
  • 4
  • 29
  • 48
  • Because [TS is structural](https://www.typescriptlang.org/docs/handbook/type-compatibility.html) objects with excess properties can be used in any place as long as the required keys and values exist. e.g. https://tsplay.dev/WYLDbm – jsejcksn Mar 10 '23 at 10:40
  • Does this answer your question? [Typescript Key-Value relation preserving Object.entries type](https://stackoverflow.com/questions/60141960/typescript-key-value-relation-preserving-object-entries-type) – jsejcksn Mar 10 '23 at 10:42

2 Answers2

1

It is because the type information of key will be lost after Object.entries and turns into string.

By taking the reference from this post of keeping the type of the keys while looping an object, here is a workaround that declare the type of key before the loop.

function addListeners(el: HTMLElement, listeners: { [key in keyof HTMLElementEventMap]: Function }) {
  if (listeners) {
    let key: keyof typeof listeners;
    for (key in listeners) {
      el.addEventListener(key, listeners[key]);
    }
  }
}

After that, it is complaining type Function in incompatible to argument of addEventListener.

To fix this, you can inspect the typescript dom definition, and modify the type of listeners. Which can also strengthen the type guard of listeners (prevent mismatch of key and callback, auto-infering the type of event).

Also, { [key in keyof HTMLElementEventMap]: Function } will enforce you to implement all members of HTMLElementEventMap. To make it optional, surround it with Partial<T>

Here is a full solution in playground

Cheng Dicky
  • 498
  • 3
  • 9
0

There are two overload for addEventListener first one take first arg as key of HTMLElementEventMap and second arg as a function that will take Event |UIEvent | ...13 more as param and return any if you want to use that one then change your code to

function addListeners(el: HTMLElement, listeners: { [key in keyof HTMLElementEventMap]: (e:Event | UIEvent)=> any }) {
  //you can add other types as well after UIEvent
  if (listeners) {
    for (const [key, value] of Object.entries(listeners)) {
      el.addEventListener(key, value);
    }
  }
}

Second one take first arg as string and the second arg as EventListenerOrEventListenerObject if you want to use this one then change your code to

function addListeners(el: HTMLElement, listeners: { [key in keyof HTMLElementEventMap]: EventListenerOrEventListenerObject}) {
  if (listeners) {
    for (const [key, value] of Object.entries(listeners)) {
      el.addEventListener(key, value);
    }
  }
}

enter image description here

jitender
  • 10,238
  • 1
  • 18
  • 44