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
>Solution :
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
typethat combines the DOM’s built-inHTMLDivElementtype with a singularinfo: { readonly text: string }type.- My code below also defines
info‘s Type astype MyInfoinstead of being anonymous. - I call this
HTMLDivElementWithInfo.
- My code below also defines
-
Change the
divModifyfunction to returnHTMLDivElementWithInfoinstead ofHTMLDivElement. -
Add a type-assertion to
Object.defineProperty‘s return-value (which is an alias ofdiv) nameddiv2asHTMLDivElementWithInfo. -
After all that,
tscwill now allow you to dereferencediv.info.
Like so:
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 );