TypeScript - Namespaces
TypeScript - Namespaces
Section titled “TypeScript - Namespaces”Namespaces are a TypeScript-specific way to organize code by grouping logically related functionalities. They were particularly important before the widespread adoption of standard ES Modules (import/export) as a means to avoid polluting the global scope and prevent naming conflicts, especially in larger applications.
Recommendation: For most modern TypeScript projects, ES Modules (import/export) are the preferred standard for code organization and dependency management. Namespaces are now less common but still exist and can be useful in specific scenarios.
When to Consider Namespaces
Section titled “When to Consider Namespaces”- Legacy Codebases: You might encounter them in older TypeScript projects.
- Structuring Internal Code: Sometimes used within a larger library or application to group internal helpers or components without exposing them as separate modules.
- Global Object Augmentation: Used in declaration files (
.d.ts) to add properties or methods to existing global objects or libraries. - Simple Scripts /
--outFile: When compiling multiple.tsfiles into a single JavaScript output file using thetsc --outFileoption (less common with module systems).
Defining a Namespace
Section titled “Defining a Namespace”A namespace definition begins with the namespace keyword followed by the namespace name. Anything intended to be used outside the namespace must be explicitly exported using the export keyword.
// --- validationUtils.ts ---namespace Validation { export interface StringValidator { isValid(s: string): boolean; }
// Internal detail, not exported const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
export class EmailValidator implements StringValidator { isValid(s: string): boolean { return emailRegex.test(s); } }
export function isString(value: any): value is string { return typeof value === 'string'; }}Accessing Namespace Members
Section titled “Accessing Namespace Members”To access exported members from outside the namespace, use the namespace name followed by a dot (.) and the member name.
// --- app.ts ---
// Assuming validationUtils.ts is compiled/included
const email = "test@example.com";const validator: Validation.StringValidator = new Validation.EmailValidator();
if (validator.isValid(email)) { console.log(`'${email}' is a valid email.`);} else { console.log(`'${email}' is not a valid email.`);}
console.log(`Is 'hello' a string? ${Validation.isString('hello')}`); // true// console.log(Validation.emailRegex); // Error: Property 'emailRegex' is not exported from namespace 'Validation'.Multi-File Namespaces
Section titled “Multi-File Namespaces”Namespaces can span multiple files. TypeScript treats identically named namespaces declared in different files as merged. To ensure the compiler knows about dependencies between files when not using ES modules, you often need:
- Triple-Slash Reference Directives:
/// <reference path="./other-file.ts" />placed at the top of the dependent file. - Compilation Order or
--outFile: Ensuring files are compiled in the correct order or bundled into a single file usingtsc --outFile output.js file1.ts file2.ts ...(Note:--outFilerequires"module": "System"or"AMD"intsconfig.jsonand is incompatible withCommonJS,ESNext, etc.).
Example: Multi-File Namespace
Section titled “Example: Multi-File Namespace”// --- interfaces.ts ---namespace MyShapes { export interface Shape { getArea(): number; }}
// --- circle.ts ---/// <reference path="interfaces.ts" />namespace MyShapes { export class Circle implements Shape { constructor(public radius: number) {} getArea(): number { return Math.PI * this.radius ** 2; } }}
// --- rectangle.ts ---/// <reference path="interfaces.ts" />namespace MyShapes { export class Rectangle implements Shape { constructor(public width: number, public height: number) {} getArea(): number { return this.width * this.height; } }}
// --- main.ts ---/// <reference path="circle.ts" />/// <reference path="rectangle.ts" />
const circle = new MyShapes.Circle(5);const rect = new MyShapes.Rectangle(10, 4);
console.log(`Circle Area: ${circle.getArea()}`);console.log(`Rectangle Area: ${rect.getArea()}`);
// Compile using: tsc --outFile dist/app.js main.tsAgain, this multi-file setup is much more elegantly handled by ES Modules without needing reference directives or --outFile.
Nested Namespaces
Section titled “Nested Namespaces”You can define namespaces inside other namespaces.
namespace App { export namespace Services { export class ApiClient { fetchData() { console.log('Fetching data...'); } } } export namespace Utils { export function log(message: string) { console.log(`LOG: ${message}`); } }}
const client = new App.Services.ApiClient();client.fetchData();App.Utils.log("App started.");Namespace Aliases
Section titled “Namespace Aliases”You can create shorter aliases for nested namespaces using import alias = Namespace.NestedNamespace;
import ServiceClient = App.Services.ApiClient;import logUtil = App.Utils.log;
const client2 = new ServiceClient();logUtil("Using alias.");Note: This import = syntax is specific to importing namespaces and older module formats, distinct from the standard ES import keyword.
Conclusion
Section titled “Conclusion”While namespaces were an important part of early TypeScript, ES Modules (import/export) provide a standardized, more powerful, and widely adopted mechanism for code organization in modern JavaScript and TypeScript. Use namespaces judiciously, primarily when interacting with legacy systems or specific declaration file patterns.