TypeScript namespace


May 07, 2021 22:00 TypeScript


Table of contents


TypeScript namespace

Note about terminology: It is important to note that the term name in TypeScript 1.5 has changed. T he "internal module" is now called the "namespace". "External Module" is now referred to simply as "Module" in order to be consistent with the terminology in ECMAScript 2015 (i.e., module X { is equivalent to the now namespace X {

Introduced

This article describes how to organize your code in TypeScript using namespaces (previously called "internal modules").

As we mentioned in the terminology note, "internal modules" are now called "namespaces".

In addition, any place that uses the module keyword to declare an internal module should use namespace keyword between them.

This avoids confusing new consumers with similar names.

The first step

Let's start with a program and use this example throughout the article. Let's define a few simple string validators, assuming you use them to validate user input or external data in the form.

All the validators are in one file

interface StringValidator {
    isAcceptable(s: string): boolean;
}

let lettersRegexp = /^[A-Za-z]+$/;
let numberRegexp = /^[0-9]+$/;

class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
        return lettersRegexp.test(s);
    }
}

class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}

// Some samples to try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: StringValidator; } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
    for (let name in validators) {
        console.log(""" + s + "" " + (validators[name].isAcceptable(s) ? " matches " : " does not match ") + name);
    }
});

Namespace

As more validators are added, we need a way to organize the code so that we don't have to worry about naming conflicts with other objects while recording their types. So instead of putting them under the global namespace, we wrap the validators in a namespace.

In the following example, put all the types associated with the validator in a namespace called Validation B ecause we want these interfaces and classes to be accessible outside the namespace, we need to use export C onversely, lettersRegexp numberRegexp implementation details that do not need to be exported, so they are inalible outside the namespace. In the test code at the end of the file, you need to qualify the name of a type, such as Validation.LettersOnlyValidator

A validator that uses a namespace

namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }

    const lettersRegexp = /^[A-Za-z]+$/;
    const numberRegexp = /^[0-9]+$/;

    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }

    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}

// Some samples to try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
    for (let name in validators) {
        console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
    }
});

Detach to multiple files

As applications get bigger and bigger, we need to separate the code into different files for easy maintenance.

The namespace in the multi-file

Now, let's Validation namespace into multiple files. A lthough they are different files, they are still the same namespace and are used as if they were defined in a file. B ecause there are dependencies between different files, we have added reference labels to tell the compiler files about the association. Our test code remains the same.

Validation.ts
namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
}
LettersOnlyValidator.ts
/// <reference path="Validation.ts" />
namespace Validation {
    const lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
}
ZipCodeValidator.ts
/// <reference path="Validation.ts" />
namespace Validation {
    const numberRegexp = /^[0-9]+$/;
    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}
Test.ts
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />

// Some samples to try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
    for (let name in validators) {
        console.log(""" + s + "" " + (validators[name].isAcceptable(s) ? " matches " : " does not match ") + name);
    }
});

When it comes to multiple files, we have to make sure that all the compiled code is loaded. We have two ways.

The first way to compile all the input files into one output file requires a --outFile tag:

tsc --outFile sample.js Test.ts

The compiler automatically sorts the output based on the reference labels in the source code. You can also specify each file individually.

tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts

The second way, we can compile each file (the default), and each source file generates a JavaScript file accordingly. Then, on the <script> are introduced in the correct order through the hashtag, such as:

MyTestPage.html (excerpt)
    <script src="Validation.js" type="text/javascript" />
    <script src="LettersOnlyValidator.js" type="text/javascript" />
    <script src="ZipCodeValidator.js" type="text/javascript" />
    <script src="Test.js" type="text/javascript" />

Alias

Another way to simplify namespace operations is to import q = x.y.z give commonly used objects a short name. D on't confuse it with import x = require('name') which here creates an alias for the specified symbol. You can create aliases for any identifier in this way, including objects in imported modules.

namespace Shapes {
    export namespace Polygons {
        export class Triangle { }
        export class Square { }
    }
}

import polygons = Shapes.Polygons;
let sq = new polygons.Square(); // Same as "new Shapes.Polygons.Square()"

Note that instead of using require keyword, we use the qualified name assignment of the import symbol directly. T his is similar to var but it also applies to types and imported symbols with namespace meaning. Importantly, for values, import different reference than the original symbol, so changing the var does not affect the value of the original variable.

Use other JavaScript libraries

In order to describe the type of class library that is not written in TypeScript, we need to declare the API exported by the class library. Because most libraries provide only a few top-level objects, namespaces are a good way to represent them.

We call it a declaration because it is not a specific implementation of an external program. W e usually .d.ts I f you're familiar with C/C, you can think of them as .h files. Let's look at some examples.

The external namespace

The popular library D3 defines its d3 global object d3. B ecause the library is <script> (not through a module loader), its declaration file uses internal modules to define its type. I n order for the TypeScript compiler to recognize its type, we use an external namespace declaration. For example, we can write something like this:

D3.d.ts (partial excerpt)
declare namespace D3 {
    export interface Selectors {
        select: {
            (selector: string): Selection;
            (element: EventTarget): Selection;
        };
    }

    export interface Event {
        x: number;
        y: number;
    }

    export interface Base extends Selectors {
        event: Event;
    }
}

declare let d3: D3.Base;