Modules

The module pattern has become increasingly popular in JavaScript development. It provides an easy way to encapsulate private members, something that isn't inherently available in JavaScript, and keep objects off the global namespace. In TypeScript there are two types of modules: internal modules and external modules. We will discuss them both in detail, but for now let's just talk about internal modules.

Internal modules

Internal modules represent a namespace that classes, interfaces, enums, variables, code segments, and other namespaces can exist inside of. They are created inside of a closure just like classes are. However, modules don't return a function that gets assigned to a global variable. Modules execute the closure with a global variable as a parameter. Exported properties are then attached to this global variable. A module can contain any number of declarations of any type of object including other modules. Each of the declarations inside of a module can be either kept isolated or they can be exported. Exported objects will be added to the module's instance. In the following code segment, you can see an internal module definition:

module Shapes {
    var origin: IPoint = new Point(0, 0);
    export interface IShape {
        Type: ShapeType;
        Bounds: IBounds;
    }
    export class Shape implements IShape {
        public Type: ShapeType = ShapeType.Rectangle;
        public Bounds: IBounds = new Bounds();
    }     
}

In this example, we see a mix of both private members and public members that exist on the Shapes module. The origin variable is a private instance of the Point class we created earlier. Any expression inside the Shapes module has access to the origin variable but since it is not decorated with the export keyword, it is limited to within the module. We have defined an interface for an object type that will represent a shape of some sort. Then we also have a new class that implements the shape interface and is ready to be drawn on a surface. This Shape object needs to be accessible outside the Shapes namespace so we must export it. Our ShapeType enum probably belongs within the Shapes namespace so let's move its declaration to within the module definition as well:

module Shapes {
    var origin: IPoint = new Point(0, 0);
    export enum ShapeType {
        Rectangle = 3,
        Circle,
        Line,
        Freehand,
    }
    export interface IShape {
        Type: ShapeType;
        Bounds: IBounds;
    }
    export class Shape {
        public Type: ShapeType = ShapeType.Rectangle;
        public Bounds: IBounds = new Bounds();
    }     
}

We now have an internal module that has two accessible named types, the ShapeType enum and Shape class, and one inaccessible variable, the origin variable, which is part of the isolated state of the module. While we may not necessarily need access to the ShapeType enum it still must be exported for our code to compile. Any named types that are used in the definition of another member must be made as accessible as that member. If this requirement is not met compilation will fail. Accessing these types is done by providing the module name and then the type you wish to access. In the following example, you can see we create a new Shape object and set its type:

var shape = new Shapes.Shape();
shape.Type = Shapes.ShapeType.Circle;

The JavaScript generated for this module looks very similar to the code generated for enum types. All of the members of the module are wrapped in a closure that runs when the module is loaded. Each of the exported types is placed on the instance variable that is passed to the closure that it is available to anyone accessing the module. Modules also share the same ability to be merged that enums do. This means that you can separate your code into multiple files and have them merged into one object during runtime. The downside to this implementation of modules is that it takes away the ability to create interfaces for your modules. However, the ability to organize your code in whichever manner you choose is far more valuable than any functionality interfaces would bring to modules. Here, you can see the resulting JavaScript that TypeScript creates to define its modules:

var Shapes;
(function (Shapes) {
    var origin = new Point(0, 0);
    (function (ShapeType) {
        ShapeType[ShapeType["Rectangle"] = 3] = "Rectangle";
        ShapeType[ShapeType["Circle"] = 4] = "Circle";
        ShapeType[ShapeType["Line"] = 5] = "Line";
        ShapeType[ShapeType["Freehand"] = 6] = "Freehand";
    })(Shapes.ShapeType || (Shapes.ShapeType = {}));
    var ShapeType = Shapes.ShapeType;
    var Shape = (function () {
        function Shape() {
            this.Type = 3 /* Rectangle */;
            this.Bounds = new Bounds();
            this.Bounds.Location = origin;
        }
        return Shape;
    })();
    Shapes.Shape = Shape;
})(Shapes || (Shapes = {}));

So far we've only seen a small example of a module, but imagine building a large-scale application with a large amount of object types. You will want to create namespaces to organize your code and create a layer of separation between segments of code that are unrelated. There are two ways to provide module namespaces in TypeScript. We can nest the modules inside of each other or we can define them as part of the module declaration.

Let's take a look at some of the different ways to define namespaces and create module definitions.

module Animals {
    export module Reptiles {
        export var snake = "snake";
    }
}
module Animals.Mammals {
    export var monkey = "monkey";
}

In this example, we create a declaration for the Animals module and then nest another module declaration inside of it that gets exported. There is also a module declaration that is given a multi-part type name separated by the . character. Both of the module declarations shown are valid. The JavaScript output from both declarations is exactly the same so which style you choose is really a matter of preference. The compiler will treat them as equivalents as will the development environment. As you can see in the following screenshot, accessing both the Reptiles and Mammals namespaces is identical:

Internal modules

The resulting JavaScript from each of these module definitions is identical in structure. However, if you intend on declaring multiple nested modules inside of a single TypeScript file, it is best to use the expanded nesting declaration rather than the shortcut. The compiler will not attempt to optimize and merge the module definitions when it creates the JavaScript output. As you can see in the following JavaScript code, you will unnecessarily have multiple closures adding objects to the type object. This is inevitable if you intend on separating your namespaces by file.

var Animals;
(function (Animals) {
    (function (Reptiles) {
        Reptiles.snake = "snake";
    })(Animals.Reptiles || (Animals.Reptiles = {}));
    var Reptiles = Animals.Reptiles;
})(Animals || (Animals = {}));
var Animals;
(function (Animals) {
    (function (Mammals) {
        Mammals.monkey = "monkey";
    })(Animals.Mammals || (Animals.Mammals = {}));
    var Mammals = Animals.Mammals;
})(Animals || (Animals = {}));

There are probably some common object types or utility methods that you would prefer to have quick access to inside of another module. As we continue to separate our types appropriately let's create a new module for drawing-related functionality. This module will contain both the Point class and the Bounds class that we created earlier. Both of these classes will be exported because other modules will need to be able to reference them. While it is nice to have the code separated into isolated segments that can be easily managed, I now have to provide a fully qualified name every time I wish to use a type from another module. Thankfully, TypeScript has provided a keyword opposite to the export keyword that allows us to create an alias to members of another module. The import keyword creates a new local reference to exported types from other modules. In the following code segment, we will create an alias for the Bounds class inside the Shapes module. This will prevent us from having to change the definition of our Shape class to contain the full namespace of the Bounds type.

module Shapes {
    import Bounds = Drawing.Bounds;
    export enum ShapeType {
        Rectangle = 3,
        Circle,
        Line,
        Freehand,
    }
    export interface IShape {
        Type: ShapeType;
        Bounds: Drawing.IBounds;
    }
    export class Shape {
        public Type: ShapeType = ShapeType.Rectangle;
        public Bounds: Drawing.IBounds = new Bounds();
    }     
}

As you can see, the alias can be used as both a type annotation and as its object type. The resulting JavaScript just replaces the import keyword with var and lets the runtime engine create the reference to the exported member. However, doing this directly in TypeScript will generate a compile error. The import keyword informs the compiler not to inspect just the object being aliased but its type information as well so that it can be used in type declarations.

Tip

The export and import keywords can be used in conjunction to create externally accessible aliases.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset