TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
105: Using lib.dom.d.ts Effectively
I see this all the time when developers move from JavaScript to TypeScript: they write a line of code that works perfectly in the browser, but TypeScript marks it with a red squiggly line. They assume the type definitions in the library are either wrong or unnecessarily restrictive. Specifically, they think that if they've written a CSS selector for an input field, TypeScript should just "know" that the resulting object has a .value property.
Thinking querySelector just knows what you're picking
Let's look at a common snippet. You've got a search bar in your HTML, and you want to grab the text when a user hits a button. You might write this:
const searchInput = document.querySelector('.search-box');
console.log(searchInput.value); // ❌ Error: Property 'value' does not exist on type 'Element | null'.
The immediate reaction is often: "But I know it's an input! Why is TypeScript fighting me?" The misconception here is that lib.dom.d.ts is a magic mirror that looks at your HTML file. It isn't. TypeScript has no idea what your HTML looks like at runtime. All it knows is that querySelector is designed to find any element, and the most specific type it can safely guarantee is Element. Since a <div> or a <span> doesn't have a .value property, TypeScript blocks you from accessing it to prevent a runtime crash.
Navigating the DOM Type Hierarchy
To fix this, you have to understand how lib.dom.d.ts is structured. It's not just one big bucket of types; it's a hierarchy. At the top, you have Node, then Element, then HTMLElement, and finally, the specific specialized types like HTMLInputElement, HTMLButtonElement, or HTMLCanvasElement.
When you use querySelector, you're getting a generic Element. To get to the .value property, you need to tell TypeScript that this specific element is an HTMLInputElement. I usually do this with a type assertion:
const searchInput = document.querySelector('.search-box') as HTMLInputElement;
console.log(searchInput.value); // ✅ This works now.
I'll be honest: assertions (the as keyword) are a bit of a shortcut. You're essentially telling the compiler, "Trust me, I know what I'm doing." If you change your HTML later and .search-box becomes a <div>, TypeScript won't warn you, and your code will crash at runtime. If you want to be truly robust, use a type guard.
Handling the 'Null' Reality of the DOM
Even if you fix the element type, you're still dealing with the fact that querySelector returns null if the element isn't found. This is where many developers get lazy and use the non-null assertion operator (!), but in a professional codebase, that's a recipe for Cannot read property 'value' of null errors.
The most effective way to use the DOM library is to combine a null check with an instanceof check. This narrows the type dynamically and safely:
const searchInput = document.querySelector('.search-box');
if (searchInput instanceof HTMLInputElement) {
// Inside this block, TypeScript knows searchInput is NOT null
// AND that it is specifically an HTMLInputElement.
console.log(searchInput.value);
} else {
console.error("Search input not found or is the wrong element type.");
}
This approach is the "gold standard." You aren't lying to the compiler; you're proving to the compiler that the object is what you say it is. I highly recommend browsing the lib.dom.d.ts file (you can usually Cmd/Ctrl+Click on HTMLElement in your IDE) to see the sheer number of specific element types available. Whether it's HTMLSelectElement for dropdowns or HTMLImageElement for images, there is almost always a specific interface tailored to the properties of that HTML tag.
📋 Practical Task
Exercise: Typing a Dynamic Form Validation Utility
You are building a small validation script. You have a form containing a text input (#username), a checkbox (#terms), and a display area (#error-message) which is a <div>.
Your task is to write a function called validateForm that does the following:
- Uses
document.querySelectorto grab all three elements. - Uses
instanceofchecks to ensure the elements are the correct types (HTMLInputElementfor the username and checkbox, andHTMLDivElementfor the error message). - Checks if the username is empty OR if the terms checkbox is NOT checked.
- If validation fails, it sets the
textContentof the error message div to "Please provide a username and accept the terms." - If validation passes, it clears the error message text.
Requirement: Do not use as any or the ! operator. Use proper type narrowing to ensure the code is completely type-safe.
There are no comments for now.