typescript

With the increasing use of third-party libraries in software, we often encounter the problem of global namespaces being polluted, leading to name conflicts between components in the global namespace. As a result, we need to use namespaces to organize blocks of code in order to uniquely identify variables, objects, and classes.

In this article, we will discuss namespaces, when they are needed, and how they can be used to enhance the organization of TypeScript code.

What are namespaces?

Namespaces are paradigms for organizing code so that variables, functions, interfaces, or classes are grouped together at local scope to avoid naming conflicts between components at global scope. This is one of the most common strategies for reducing global scope pollution.

While modules are also used for code organization, namespaces are easily used for simple implementations. Modules provide some additional benefits such as strong code isolation, strong support for packaging, and component redirection.

Why do we need namespaces?

Namespaces have the following advantages.

  • Code reusability - the importance of namespaces for code reusability cannot be underestimated
  • Inflated global scope - namespaces reduce the amount of code in the global scope, making it less bloated
  • Third-party libraries - as the number of sites that rely on third-party libraries continues to grow, it’s important to protect your code with namespaces to prevent same-name conflicts between your code and third-party libraries
  • Distributed development - With the popularity of distributed development, pollution is almost inevitable because developers can more easily use public variables or class names. This can lead to name conflicts and global scope pollution

Design considerations for using namespaces

Implicit dependency order

Using namespaces when using certain external libraries will require implicitly implementing dependencies between your code and those libraries. This causes you the stress of managing dependencies yourself in order to load them correctly, as dependencies can be prone to errors. If you find yourself in this situation, using modules can take the pressure off you.

Node.js applications

For Node.js applications, it is recommended to use modules rather than namespaces, as modules are the standard for packaging and code organization in Node.

Non-JavaScript content import

It is recommended to use modules rather than namespaces when working with non-JavaScript content, as some module loaders (such as SystemJS and AMD) allow importing non-JavaScript content.

Legacy code

When working with code bases that are no longer designed but are continually patched, it is recommended to use namespaces rather than modules.

In addition, namespaces come in handy when porting older JavaScript code.

Exploring Namespaces in TypeScript

Now that we have a common understanding of what TypeScript namespaces are and why we need them, we can take a deeper look at how to use them.

Given that TypeScript is a superset of JavaScript, its concept of namespaces is derived from JavaScript.

By default, JavaScript does not have namespaces, because we have to use IIFE (Immediate Invocation of Function Expressions) to implement namespaces.

1
2
3
4
var Vehicle;
(function (Vehicle) {
    let name = "car";
})(Vehicle || (Vehicle = {}));

This is the bulk of the code used to define namespaces. Meanwhile, TypeScript handles it differently.

Single-file namespaces

In TypeScript, namespaces are defined using the namespace keyword followed by the name of choice.

A single TypeScript file can have as many namespaces as needed.

1
2
namespace Vehicle {}
namespace Animal {}

As we have seen, the TypeScript namespace is a piece of syntactic sugar compared to our JavaScript implementation that uses IIFE’s namespace.

Functions, variables and classes can be defined within namespaces as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
namespace Vehicle {
  const name = "Toyota"
  function getName () {
      return `${name}`
  }
}
namespace Animal {
  const name = "Panda"
  function getName () {
      return `${name}`
  }
}

The above code allows us to use the same variable and function names without conflicts.

Accessing functions, variables, objects and classes outside their namespace

In order to access a function or class outside its namespace, export must be preceded by the keyword before the function or class name, as shown below.

1
2
3
4
5
6
namespace Vehicle {
  const name = "Toyota"
  export function getName () {
      return `${name}`
  }
}

Note that we have to omit the keyword export variable because it should not be accessed outside the namespace.

Now, we can getName access the function as follows.

1
Vehicle.getName() //Toyota

Organizing code using nested namespaces

TypeScript allows us to use nested namespaces to organize our code.

We can create nested namespaces, as shown below.

1
2
3
4
5
6
7
8
namespace TransportMeans {
  export namespace Vehicle {
    const name = "Toyota"
    export function getName () {
        return `${name}`
    }
  }
}

Note the keyword Vehicle in front of the namespace export. This allows access to the TransportMeans namespace outside of the namespace.

We can also perform deep nesting of namespaces.

Our nested namespaces can be accessed as follows.

1
TransporMeans.Vehicle.getName() // Toyota

Namespace aliases

For deeply nested namespaces, namespace aliases can come in handy to keep things tidy.

Namespace aliases are defined using the import keyword, as follows.

Import car name = mode of transportation. Vehicle; CarName. GetName(); // Toyota

1
2
import carName= TransporMeans.Vehicle;
carName.getName(); //Toyota

Multiple file namespaces

Namespaces can be shared between multiple TypeScript files. This is done through the reference tag.

Consider the following.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//constant.ts
export const name = "Toyota"


//vehicle.ts
<reference path = "constant.ts" />
export namespace Vehicle {
  export function getName () {
      return `${name}`
  }
}

Here, we must reference the constant.ts file in order to access the name.

1
2
3
4
//index.ts
<reference path = "constant.ts" />
<reference path = "vehicle.ts" />
Vehicle.getName() // Toyota

Notice how we start our references with the highest level namespace. This is how references in the multi-file interface are handled. typeScript will use this order when compiling the file.

We can use the following command to instruct the compiler to compile our multi-file TypeScript code into a single JavaScript file.

1
tsc --outFile index.js index.ts

Using this command, the TypeScript compiler will generate a file with the name index.js

Conclusion

To build extensible and reusable TypeScript applications, TypeScript namespaces are very handy because they improve the organization and structure of our applications.