Angular Working with JavaScript Modules

You are currently viewing Angular Working with JavaScript Modules

JavaScript modules are used to manage the dependencies in a web application, which means you don’t need to manage a large set of individual code files to ensure that the browser downloads all the code for the application. Instead, during the compilation process, all of the JavaScript files that the application requires are combined into a larger file, known as a bundle, and it is this that is downloaded by the browser.

NOTE: Older versions of Angular relied on a module loader, which would send separate HTTP requests for the JavaScript files required by an application. Changes to the development tools have simplified this process and switch to using bundles created during the build process.

Creating and Using Modules

Each TypeScript or JavaScript file that you add to a project is treated as a module. To demonstrate, I created a folder called modules in the src folder, added to it a file called NameAndWeather.ts, and added the code shown in the following example.

export class Name {
 constructor(first, second) {
 this.first = first;
 this.second = second;
 }
 get nameMessage() {
 return `Hello ${this.first} ${this.second}`;
 }
}
export class WeatherLocation {
 constructor(weather, city) {
 this.weather = weather;
 this.city = city;
 }
 get weatherMessage() {
 return `It is ${this.weather} in ${this.city}`;
 }
}

The classes, functions, and variables defined in a JavaScript or TypeScript file can be accessed only within that file by default. The export keyword is used to make features accessible outside of the file so that they can be used by other parts of the application. In the example, I have applied the export keyword to the Name and WeatherLocation classes, which means they are available to be used outside of the module.

The import keyword is used to declare a dependency on the features that a module provides. In the following example, I have used Name and WeatherLocation classes in the main.ts file, and that means I have to use the import keyword to declare a dependency on them and the module they come from.

import { Name, WeatherLocation } from "./modules/NameAndWeather";
let name = new Name("Adam", "Freeman");
let loc = new WeatherLocation("raining", "London");
console.log(name.nameMessage);
console.log(loc.weatherMessage);

This is the way that I use the import keyword in most of the examples on this website. The keyword is followed by curly braces that contain a comma-separated list of the features that the code in the current files depends on, followed by the keyword, followed by the module name. In this case, I have imported the Name and WeatherLocation classes from the NameAndWeather module in the modules folder. Notice that the file extension is not included when specifying the module.

When the changes to the main.ts file are saved, the Angular development tools build the project and see that the code in the main.ts file depends on the code in the NameAndWeather.ts file. This dependency ensures that the Name and WeatherLocation classes are included in the JavaScript bundle file, and you will see the following output in the browser’s JavaScript console, showing that code in the module was used to produce the result:

Hello Adam Freeman

It is raining in London

Notice that I didn’t have to include the NaneAndWeather.ts file in a list of files to be sent to the browser. Just using the import keyword is enough to declare the dependency and ensure that the code required by the application is included in the JavaScript file sent to the browser.

(You will see errors warning you that properties have not been defined. Ignore those warnings for the moment; I explain how they are resolved later in the chapter.)



UNDERSTANDING MODULE RESOLUTION

You will see two different ways of specifying modules in the import statements on this website. The first is a relative module, in which the name of the module is prefixed with ./, like this above example.

...
import { Name, WeatherLocation } from "./modules/NameAndWeather";
...

This statement specifies a module located relative to the file that contains the import statement. In this case, the NameAndWeather.ts file is in the modules directory, which is in the same directory as the main.ts file. The other type of import is nonrelative.

...
import { Component } from "@angular/core";
...

The module in this import statement doesn’t start with ./, and the build tools resolve the dependency by looking for a package in the node_modules folder. In this case, the dependency is on a feature provided by the @angular/core package, which is added to the project when it is created by the ng new command.

Renaming Imports

In complex projects that have lots of dependencies, it is possible that you will need to use two classes with the same name from different modules. To re-create this situation, I created a file called DuplicateName.ts in the src/modules folder and defined the class as shown in the following example.

export class Name {
 get message() {
 return "Other Name";
 }
}

This class doesn’t do anything useful, but it is called Name, which means that importing it using the approach in the above example will cause a conflict because the compiler won’t be able to differentiate between the two classes with that name. The solution is to use the keyword, which allows an alias to be created for a class when it is imported from a module, as shown in the following example.

import { Name, WeatherLocation } from "./modules/NameAndWeather";
import { Name as OtherName } from "./modules/DuplicateName";
let name = new Name("Adam", "Freeman");
let loc = new WeatherLocation("raining", "London");
let other = new OtherName();
console.log(name.nameMessage);
console.log(loc.weatherMessage);
console.log(other.message);

The Name class in the DupliateName module is imported as OtherName, which allows it to be used without conflicting with the Name class in the NameAndWeather module. This example produces the following output:

Hello Adam Freeman

It is raining in London

Other Name

Importing All of the Types in a Module

An alternative approach is to import the module as an object that has properties for each of the types it contains, as shown in the following example

import * as NameAndWeatherLocation from "./modules/NameAndWeather";
import { Name as OtherName } from "./modules/DuplicateName";
let name = new NameAndWeatherLocation.Name("Adam", "Freeman");
let loc = new NameAndWeatherLocation.WeatherLocation("raining", "London");
let other = new OtherName();
console.log(name.nameMessage);
console.log(loc.weatherMessage);
console.log(other.message);

The import statement in this example imports the contents of the NameAndWeather module and creates an object called NameAndWeatherLocation. This object has Name and Weather properties that correspond to the classes defined in the module. This example produces the same output as the above example.



Leave a Reply