1

In typescript, an html element is typed with HTMLElement. But what if I add a property to that HTML element, the HTMLElement type will fail. How can I type an html element with a new property. Here is the code where I get the error:

function divModify():HTMLElement{
  const div:HTMLElement= document.createElement("div");
  div.id= "test";
  div.classList.add("d-flex");
  const info={text: "Hello word"}
  Object.defineProperty(div,"info",{value:info})
  console.log(div)
  console.log(div.info)
  return div
}

divModify()

the error:

Property 'info' does not exist on type 'HTMLElement'.

and a playground of the code: code playground

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
Usiel
  • 671
  • 3
  • 14
  • 1
    You don't need `const div: HTMLElement` - TypeScript already knows that `document.createElement('div')` returns a `HTMLDivElement` - you can change it to just `const div = document.createElement("div");` – Dai Jun 07 '22 at 23:22
  • "Property 'info' does not exist on type '`HTMLElement`'." - the error is correct. There is no `HTMLElement.info` property. TypeScript's flow-analysis is amazing, but unfortunately it's still not yet sophisticated enough to track properties added or removed from object instances. – Dai Jun 07 '22 at 23:24
  • So, how could I type this variable. – Usiel Jun 07 '22 at 23:27
  • 1
    Why are you wanting a custom property on an element in the first place? – Daniel A. White Jun 07 '22 at 23:28

1 Answers1

2

While TypeScript's flow-analyzer is very impressive and allows for types to be refined, narrowed, or widened within a scope when you use type-guard type predicate functions, typeof and instanceof, TypeScript still doesn't yet track all changes to a variable's type which should otherwise be inferable from use of the in operator or Object.defineProperty.

(UPDATE: So TypeScript 4+ does use in for narrowing, but only to discriminate between possibilities in union types)

...so until TypeScript supports following Object.defineProperty you can workaround that limitation with some manual tweaks:

  • Manually defining a new type that combines the DOM's built-in HTMLDivElement type with a singular info: { readonly text: string } type.

    • My code below also defines info's Type as type MyInfo instead of being anonymous.
    • I call this HTMLDivElementWithInfo.
  • Change the divModify function to return HTMLDivElementWithInfo instead of HTMLDivElement.

  • Add a type-assertion to Object.defineProperty's return-value (which is an alias of div) named div2 as HTMLDivElementWithInfo.

  • After all that, tsc will now allow you to dereference div.info.

Like so:

Playground link.

type MyInfo                 = { readonly text: string; };
type HTMLDivElementWithInfo = HTMLDivElement & { info: MyInfo };

function createDivWithInfo(): HTMLDivElementWithInfo {

  const div = document.createElement("div");
  div.id = "test";
  div.classList.add("d-flex");

  const initialInfoPropValue = { text: "Hello word" };
  const div2 = Object.defineProperty( div, "info", { value: initialInfoPropValue } ) as HTMLDivElementWithInfo;

  console.log( div2 );
  console.log( div2.info );
  return div2;
}

const d = createDivWithInfo();
console.log( d.info );
Dai
  • 141,631
  • 28
  • 261
  • 374