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
50: Ambient Declarations
I was working on a legacy project the other day where the team had this old, proprietary analytics library—let's call it LegacyStats. It wasn't an npm package; it was just a 50KB JS file loaded via a <script> tag in the HTML head. It just dumps a global LegacyStats object onto the window.
The "Cannot find name" wall
Naturally, as soon as I tried to use it in a TypeScript file, the compiler lost its mind. I wrote this:
const report = LegacyStats.generateReport('q3_sales');
console.log(report);
Right away, I get the dreaded: Cannot find name 'LegacyStats'.
Now, if you run this code in the browser, it works perfectly. The library is there, the function exists. But TypeScript is a pessimist. If it can't see a const, let, class, or an import statement that defines LegacyStats, it assumes it doesn't exist. It doesn't know about your HTML file or the random scripts you loaded in the browser.
Temptation of the any cast
My first instinct when I'm in a rush is usually to just cheat. I can tell TypeScript to stop complaining by casting the window object to any:
const report = (window as any).LegacyStats.generateReport('q3_sales');
The error goes away. Great. But now I have no autocomplete. I have no idea if generateReport takes a string, a number, or a configuration object. I'm basically writing JavaScript again, which defeats the whole reason we're using TypeScript. I want the safety, but I can't actually provide the implementation because the code lives in that external JS file.
Defining the ghost in the machine
This is where "ambient declarations" come in. I need a way to tell TypeScript: "Hey, I'm not defining this variable right now, but I promise you it will exist when the code actually runs."
The keyword for this is declare. I tried adding this to the top of my file:
declare const LegacyStats: any;
const report = LegacyStats.generateReport('q3_sales');
The error disappears, and it's slightly cleaner than the window as any hack. But it's still any. I know the library has a generateReport method that takes a string and returns an object with a total and a count. Let's actually describe it.
interface StatsReport {
total: number;
count: number;
}
interface LegacyStatsLib {
generateReport(id: string): StatsReport;
clearCache(): void;
}
declare const LegacyStats: LegacyStatsLib;
Now, when I type LegacyStats., my IDE actually suggests generateReport and clearCache. This is the magic of ambient declarations. I've described the shape of the code without actually providing the logic.
Moving to the .d.ts file
Putting these declare statements at the top of every file where I use the library would be a nightmare. It's repetitive and clutters the logic. I noticed that TypeScript treats files ending in .d.ts (declaration files) differently. They are intended specifically for these types of ambient descriptions.
I created a file called globals.d.ts and moved my declarations there:
// globals.d.ts
interface StatsReport {
total: number;
count: number;
}
interface LegacyStatsLib {
generateReport(id: string): StatsReport;
clearCache(): void;
}
declare const LegacyStats: LegacyStatsLib;
The moment I saved that file, the LegacyStats object became available globally across my entire project. I didn't have to import it, and I didn't have to declare it in my main .ts files. TypeScript just "knows" it's there. This is how the @types` packages work under the hood—they are essentially just massive collections of .d.ts files telling TypeScript how external JS libraries are shaped.
📋 Practical Task
Fixing the Missing Types for the Legacy Maps Library
You are working on a project that uses an old global mapping library called QuickMap. The library is loaded via a CDN in the HTML, so TypeScript is currently flagging every use of QuickMap as an error.
Your goal: Create an ambient declaration that allows the project to compile and provides full type safety for the library.
The QuickMap object has the following API:
1. A method init(elementId: string, apiKey: string): void
2. A method setZoom(level: number): void
3. A property currentCenter which is an object containing lat: number and lng: number.
Requirements:
- Create a
declarations.d.tsfile. - Define an interface for the
QuickMapobject. - Use the
declarekeyword to make theQuickMapconstant globally available. - In a separate
main.tsfile, call theinitmethod and log thecurrentCenter.latto the console to verify that the types are working.
There are no comments for now.