Working with DOM & Browser APIs¶
JavaScript’s DOM APIs are powerful but notoriously error‑prone. You can query any element, cast it to anything, and call methods that may or may not exist. TypeScript fixes this by providing rich built‑in DOM types, strong event typing, and safe narrowing techniques that prevent runtime crashes.
Let’s explore how to use TypeScript effectively in browser‑based code.
Built‑in DOM Types¶
TypeScript ships with a full set of type definitions for the browser environment. These include:
DocumentHTMLElementHTMLInputElementHTMLButtonElementNodeListEvent,MouseEvent,KeyboardEvent, etc.
Example: Querying elements¶
const button = document.querySelector("button")
Hover over button in your editor:
const button: HTMLButtonElement | null
TypeScript knows:
- the element is a
HTMLButtonElement - it might be
null
Example: Querying by ID¶
const input = document.getElementById("username")
TypeScript infers:
HTMLElement | null
But you often want a more specific type:
const input = document.getElementById("username") as HTMLInputElement
Or safer:
const input = document.querySelector<HTMLInputElement>("#username")
This avoids unsafe casts.
Event Typing¶
TypeScript provides strong types for DOM events.
Basic event listener¶
const button = document.querySelector("button")
button?.addEventListener("click", (event) => {
// event: MouseEvent
console.log(event.clientX)
})
TypeScript automatically infers:
"click"→MouseEvent"keydown"→KeyboardEvent"input"→InputEvent
Keyboard event example¶
document.addEventListener("keydown", (event) => {
console.log(event.key) // event: KeyboardEvent
})
Input event example¶
const input = document.querySelector("input")
input?.addEventListener("input", (event) => {
console.log(event.data) // event: InputEvent
})
Why this matters¶
- No more guessing event types
- Autocomplete for event properties
- Prevents calling invalid properties on events
Narrowing Event Targets¶
One of the most common DOM bugs in JavaScript is assuming the event target is a specific element type.
Example of unsafe JS:
document.addEventListener("input", (e) => {
console.log(e.target.value) // ❌ Might not exist
})
TypeScript correctly warns:
Property 'value' does not exist on type 'EventTarget'
Safe narrowing with instanceof¶
document.addEventListener("input", (event) => {
const target = event.target
if (target instanceof HTMLInputElement) {
console.log(target.value) // ✔ Safe
}
})
Narrowing with type predicates¶
You can write reusable guards:
function isInput(el: EventTarget | null): el is HTMLInputElement {
return el instanceof HTMLInputElement
}
document.addEventListener("input", (event) => {
if (isInput(event.target)) {
console.log(event.target.value)
}
})
Narrowing with currentTarget¶
currentTarget is often safer than target:
button.addEventListener("click", (event) => {
const btn = event.currentTarget // HTMLButtonElement
console.log(btn.disabled)
})
TypeScript knows that currentTarget is the element the listener was attached to.
Putting It All Together¶
Here’s a realistic example combining DOM types, event typing, and narrowing:
const form = document.querySelector("form")
const input = document.querySelector<HTMLInputElement>("#email")
form?.addEventListener("submit", (event) => {
event.preventDefault()
if (!input) return
const value = input.value.trim()
if (value.includes("@")) {
console.log("Valid email:", value)
} else {
console.log("Invalid email")
}
})
TypeScript ensures:
formandinputmay benullsubmitevent is aSubmitEventinput.valueis always a string- No unsafe property access
This is exactly the kind of safety you want in real browser code.
Summary¶
In this lesson, you learned how TypeScript enhances DOM programming:
1. Built‑in DOM types¶
- Strong typing for elements, events, and browser APIs
- Safer element queries
- Better autocomplete and refactoring
2. Event typing¶
- Automatic inference based on event type
- Strongly typed event objects
- No more guessing event properties
3. Narrowing event targets¶
- Use
instanceoffor safe narrowing - Use
currentTargetwhen possible - Write reusable type guards
These techniques dramatically reduce runtime errors in browser code and make DOM programming far more predictable.