Was this page helpful?

DOM Manipulation

DOM Manipulation

An exploration into theHTMLElement type

In the 20+ years since its standardization, JavaScript has come a very long way. While in 2020, JavaScript can be used on servers, in data science, and even on IoT devices, it is important to remember its most popular use case: web browsers.

Websites are made up of HTML and/or XML documents. These documents are static, they do not change. TheDocument Object Model (DOM) is a programming interface implemented by browsers to make static websites functional. The DOM API can be used to change the document structure, style, and content. The API is so powerful that countless frontend frameworks (jQuery, React, Angular, etc.) have been developed around it to make dynamic websites even easier to develop.

TypeScript is a typed superset of JavaScript, and it ships type definitions for the DOM API. These definitions are readily available in any default TypeScript project. Of the 20,000+ lines of definitions inlib.dom.d.ts, one stands out among the rest:HTMLElement. This type is the backbone for DOM manipulation with TypeScript.

You can explore the source code for theDOM type definitions

Basic Example

Given a simplifiedindex.html file:

html
<!DOCTYPEhtml>
<htmllang="en">
<head><title>TypeScript Dom Manipulation</title></head>
<body>
<divid="app"></div>
<!-- Assume index.js is the compiled output of index.ts -->
<scriptsrc="index.js"></script>
</body>
</html>

Let’s explore a TypeScript script that adds a<p>Hello, World!</p> element to the#app element.

ts
// 1. Select the div element using the id property
constapp =document.getElementById("app");
// 2. Create a new <p></p> element programmatically
constp =document.createElement("p");
// 3. Add the text content
p.textContent ="Hello, World!";
// 4. Append the p element to the div element
app?.appendChild(p);

After compiling and running theindex.html page, the resulting HTML will be:

html
<divid="app">
<p>Hello, World!</p>
</div>

TheDocument Interface

The first line of the TypeScript code uses a global variabledocument. Inspecting the variable shows it is defined by theDocument interface from thelib.dom.d.ts file. The code snippet contains calls to two methods,getElementById andcreateElement.

Document.getElementById

The definition for this method is as follows:

ts
getElementById(elementId:string):HTMLElement |null;

Pass it an element id string and it will return eitherHTMLElement ornull. This method introduces one of the most important types,HTMLElement. It serves as the base interface for every other element interface. For example, thep variable in the code example is of typeHTMLParagraphElement. Also, take note that this method can returnnull. This is because the method can’t be certain pre-runtime if it will be able to actually find the specified element or not. In the last line of the code snippet, the newoptional chaining operator is used to callappendChild.

Document.createElement

The definition for this method is (I have omitted thedeprecated definition):

ts
createElement<KextendskeyofHTMLElementTagNameMap>(tagName:K,options?:ElementCreationOptions):HTMLElementTagNameMap[K];
createElement(tagName:string,options?:ElementCreationOptions):HTMLElement;

This is an overloaded function definition. The second overload is simplest and works a lot like thegetElementById method does. Pass it anystring and it will return a standard HTMLElement. This definition is what enables developers to create unique HTML element tags.

For exampledocument.createElement('xyz') returns a<xyz></xyz> element, clearly not an element that is specified by the HTML specification.

For those interested, you can interact with custom tag elements using thedocument.getElementsByTagName

For the first definition ofcreateElement, it is using some advanced generic patterns. It is best understood broken down into chunks, starting with the generic expression:<K extends keyof HTMLElementTagNameMap>. This expression defines a generic parameterK that isconstrained to the keys of the interfaceHTMLElementTagNameMap. The map interface contains every specified HTML tag name and its corresponding type interface. For example here are the first 5 mapped values:

ts
interfaceHTMLElementTagNameMap {
"a":HTMLAnchorElement;
"abbr":HTMLElement;
"address":HTMLElement;
"applet":HTMLAppletElement;
"area":HTMLAreaElement;
...
}

Some elements do not exhibit unique properties and so they just returnHTMLElement, but other types do have unique properties and methods so they return their specific interface (which will extend from or implementHTMLElement).

Now, for the remainder of thecreateElement definition:(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]. The first argumenttagName is defined as the generic parameterK. The TypeScript interpreter is smart enough toinfer the generic parameter from this argument. This means that the developer does not have to specify the generic parameter when using the method; whatever value is passed to thetagName argument will be inferred asK and thus can be used throughout the remainder of the definition. This is exactly what happens; the return valueHTMLElementTagNameMap[K] takes thetagName argument and uses it to return the corresponding type. This definition is how thep variable from the code snippet gets a type ofHTMLParagraphElement. And if the code wasdocument.createElement('a'), then it would be an element of typeHTMLAnchorElement.

TheNode interface

Thedocument.getElementById function returns anHTMLElement.HTMLElement interface extends theElement interface which extends theNode interface. This prototypal extension allows for allHTMLElements to utilize a subset of standard methods. In the code snippet, we use a property defined on theNode interface to append the newp element to the website.

Node.appendChild

The last line of the code snippet isapp?.appendChild(p). The previous,document.getElementById, section detailed that theoptional chaining operator is used here becauseapp can potentially be null at runtime. TheappendChild method is defined by:

ts
appendChild<TextendsNode>(newChild:T):T;

This method works similarly to thecreateElement method as the generic parameterT is inferred from thenewChild argument.T isconstrained to another base interfaceNode.

Difference betweenchildren andchildNodes

Previously, this document details theHTMLElement interface extends fromElement which extends fromNode. In the DOM API there is a concept ofchildren elements. For example in the following HTML, thep tags are children of thediv element

tsx
<div>
<p>Hello, World</p>
<p>TypeScript!</p>
</div>;
constdiv =document.getElementsByTagName("div")[0];
div.children;
// HTMLCollection(2) [p, p]
div.childNodes;
// NodeList(2) [p, p]

After capturing thediv element, thechildren prop will return anHTMLCollection list containing theHTMLParagraphElements. ThechildNodes property will return a similarNodeList list of nodes. Eachp tag will still be of typeHTMLParagraphElements, but theNodeList can contain additionalHTML nodes that theHTMLCollection list cannot.

Modify the HTML by removing one of thep tags, but keep the text.

tsx
<div>
<p>Hello, World</p>
TypeScript!
</div>;
constdiv =document.getElementsByTagName("div")[0];
div.children;
// HTMLCollection(1) [p]
div.childNodes;
// NodeList(2) [p, text]

See how both lists change.children now only contains the<p>Hello, World</p> element, and thechildNodes contains atext node rather than twop nodes. Thetext part of theNodeList is the literalNode containing the textTypeScript!. Thechildren list does not contain thisNode because it is not considered anHTMLElement.

ThequerySelector andquerySelectorAll methods

Both of these methods are great tools for getting lists of dom elements that fit a more unique set of constraints. They are defined inlib.dom.d.ts as:

ts
/**
* Returns the first element that is a descendant of node that matches selectors.
*/
querySelector<KextendskeyofHTMLElementTagNameMap>(selectors:K):HTMLElementTagNameMap[K] |null;
querySelector<KextendskeyofSVGElementTagNameMap>(selectors:K):SVGElementTagNameMap[K] |null;
querySelector<EextendsElement =Element>(selectors:string):E |null;
/**
* Returns all element descendants of node that match selectors.
*/
querySelectorAll<KextendskeyofHTMLElementTagNameMap>(selectors:K):NodeListOf<HTMLElementTagNameMap[K]>;
querySelectorAll<KextendskeyofSVGElementTagNameMap>(selectors:K):NodeListOf<SVGElementTagNameMap[K]>;
querySelectorAll<EextendsElement =Element>(selectors:string):NodeListOf<E>;

ThequerySelectorAll definition is similar togetElementsByTagName, except it returns a new type:NodeListOf. This return type is essentially a custom implementation of the standard JavaScript list element. Arguably, replacingNodeListOf<E> withE[] would result in a very similar user experience.NodeListOf only implements the following properties and methods:length,item(index),forEach((value, key, parent) => void), and numeric indexing. Additionally, this method returns a list ofelements, notnodes, which is whatNodeList was returning from the.childNodes method. While this may appear as a discrepancy, take note that interfaceElement extends fromNode.

To see these methods in action modify the existing code to:

tsx
<ul>
<li>First :)</li>
<li>Second!</li>
<li>Third times a charm.</li>
</ul>;
constfirst =document.querySelector("li");// returns the first li element
constall =document.querySelectorAll("li");// returns the list of all li elements

Interested in learning more?

The best part about thelib.dom.d.ts type definitions is that they are reflective of the types annotated in the Mozilla Developer Network (MDN) documentation site. For example, theHTMLElement interface is documented by thisHTMLElement page on MDN. These pages list all available properties, methods, and sometimes even examples. Another great aspect of the pages is that they provide links to the corresponding standard documents. Here is the link to theW3C Recommendation for HTMLElement.

Sources:

The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request

Contributors to this page:
EAEthan Arrowood  (6)
OTOrta Therox  (5)
SASafei Ashraf  (1)
MMateusz  (1)
IOIván Ovejero  (1)
6+

Last updated: Dec 16, 2025