3

[[WARNING]]: It looks like the question TypeScript: add type property to HtmlElement but I need to add a function to an Element Object but not add an attribute to a Node.

I try to make JavaScript to TypeScript but I am new with TypeScript, I try to add a new function to el.clickOutsideEvent, but ts do not allow me to do like this:

Vue.directive('click-outside', {
  bind: function (el: HTMLElement, binding: Object, vnode: VNode) {
    // stack-overflow: https://stackoverflow.com/a/42389266/13031497
    el.clickOutsideEvent = function (event: MouseEvent): void {
      if (!(el == event.target) ||
            event.target instanceof Node && el.contains(event.target)) {
        vnode.context[binding.expression](event);
      }
    }
    // ugly, but fix the bug:
    // 2020/11/20 - Peterlits Zo
    // When I click the the ellipsis-h buttom, it do not change anything.
    // I find that itself call this function auto, so I add this after times.
    setTimeout(() => {
      document.body.addEventListener('click', el.clickOutsideEvent)
    }, 50)
  },
  unbind: function (el: HTMLElement) {
    document.body.removeEventListener('click', el.clickOutsideEvent)
  },
})

It tell me that: Property 'clickOutsideEvent' does not exist on type 'HTMLElement'. and something else.

What should I do for it?

VLAZ
  • 26,331
  • 9
  • 49
  • 67
Peterlits Zo
  • 476
  • 1
  • 4
  • 17

3 Answers3

2

You will probably have to extent the HTMLElement type using module-augmentation.

tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    ...
    "typeRoots": [
      "./src/js/@types"
    ]
  },
  ...
}

src/js/@types/app.d.ts

interface HTMLElement {
    clickOutsideEvent(event: MouseEvent): void
}
Dafen
  • 1,122
  • 1
  • 9
  • 26
  • Well, sad to find that it raise a error with `el and el are incompatible`. I add: ``` typescritpt interface HTMLElement { clickOutsideEvent(event: MouseEvent): void; } ``` But then the original `HTMLElement` is not compatible with the new one. (How to edit code block in comment?) – Peterlits Zo Nov 25 '20 at 16:38
  • I would avoid doing it this way as this will apply to all definitions of `HTMLElement` which is not necessarily right. Imagine you have another directive that has nothing to do with mouse clicks (say `v-mouse-hover`), this `clickOutsideEvent` will also be exposed there, which is irrelevant. – Yom T. Nov 25 '20 at 17:56
2

I would create a custom interface extending from HTMLElement.

interface IHTMLElementClicketyClick extends HTMLElement {
  clickOutsideEvent?(event: MouseEvent): void;
}

Vue.directive('click-outside', {
  bind: function (el: IHTMLElementClicketyClick, binding: Object, vnode: VNode) {

  // the rest of the code 
Yom T.
  • 8,760
  • 2
  • 32
  • 49
  • It tell me that `el` and `el` are incompatible... Well, because I change the type of function `bind`, it cannot work now. – Peterlits Zo Nov 25 '20 at 16:35
  • Hm, in that case, we need to make this method optional by adding a question mark (`?`) after the method name. I edited my answer. – Yom T. Nov 25 '20 at 17:22
2

You can declare custom properties on global.

declare global {
    interface HTMLElement {
        clickOutsideEvent: (event: MouseEvent) => void
    }
}

Another bad approach is using @ts-ignore.

// @ts-ignore
el.clickOutsideEvent = clickOutsideEvent
khcpietro
  • 1,459
  • 2
  • 23
  • 42