Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a HTMLHeadingElement in TypeScript

Tags:

typescript

I am trying to create a HTMLHeadingElement. To do that I use:

const myHeading: HTMLHeadingElement = document.createElement("h3");

So this works perfectly. But when I try to create this element more dynamically, I fail doing so:

const level: number = 3;
const headingLevel: string = "h" + level;
const myHeading: HTMLHeadingElement = document.createElement(headingLevel);

So, it looks like essentially the same thing, but apparently it isn't. The error I get when hovering myHeading is:

Type 'HTMLElement' is not assignable to type 'HTMLHeadingElement'

In the first example, though, the method createElement("h3") right away returns the type HTMLHeadingElement.

Now, my first question is, how are these two commands different? And if not clear then, how can I get the same result within myHeading in the second example as shown in the first example?

like image 404
Socrates Avatar asked Jul 11 '18 15:07

Socrates


2 Answers

TypeScript has many overloads for createElement() in its lib.dom.d.ts file, so it knows that when createElement() is called with 'h3', that will return an HTMLHeadingElement:

createElement<K extends keyof HTMLElementTagNameMap>(tagName: K): HTMLElementTagNameMap[K];
createElement(tagName: string): HTMLElement;

When it doesn't know the tag name at compile time, all it knows is that the method returns an HTMLElement. You know that, however, so you can tell TypeScript:

const myHeading = document.createElement(headingLevel) as HTMLHeadingElement;
like image 185
JB Nizet Avatar answered Sep 22 '22 14:09

JB Nizet


headingLevel is declared to have type string. That means it can be any string, including, say, "canvas", which would not lead to myHeading being a HTMLHeadingElement.

You need to be more specific with your types.

const headingLevel: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"

Unfortunately, it appears TypeScript will not let you make these as follows

const level: 1 | 2 | 3 | 4 | 5 | 6

// TypeScript's types don't know how literals are concatenated 
const headingLevel: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" = "h" + level

So you'd need to transform level into its corresponding tag name using a dictionary or array or similar.

const LEVEL_MAP: {1: "h1", 2: "h2", 3: "h3", 4: "h4", 5: "h5", 6: "h6"} = {
    1: "h1",
    2: "h2",
    3: "h3",
    4: "h4",
    5: "h5",
    6: "h6",
}
const headingLevel: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" = LEVEL_MAP[level]

... It would be nice if TypeScript supported "h" + 1 being of type "h1".

like image 34
Curtis Fenner Avatar answered Sep 24 '22 14:09

Curtis Fenner