TypeScript String Enums

TypeScript String Enums Header

Derived from photo by Markus Spiske / raumrot.com, CC-BY

This post expands upon a popular issue/proposal in the TypeScript GitHub repository called 'String enums'. The proposal is to create an enum that uses string values as opposed to the currently used numeric values. After TypeScript 2.1 was released, user wallverb posted an interesting solution leveraging the new features – many thanks for the inspiration.

While the term pattern might be a bit of an oversell, this technique or trick will get you the intended result. Included is an example to demonstrate the usefulness of this solution. The article compares other TypeScript features that don't quite meet the mark and also discusses how the solution works to create the right combination of type checking and runtime behavior.

The TypeScript team might still make it easier to create 'string enums' in future versions of the language. Regardless, learning how this solution works will deepen your understanding of the new TypeScript 2.1 features.

Use Case

If you think about inputs such as dropdowns or radio buttons where the user must select a single value from multiple choices, the underlying values oftentimes map nicely to an enum data structure. For example, consider a selection of shirt sizes. You could easily define the shirt sizes with an enum:

enum Size {
    XLarge,
    Large,
    Medium,
    Small
}

This is a nice data structure with which to code. All of the related values are in one place and it's easy to access a value from the list.

To understand what TypeScript is doing, it compiles the enum to a data structure at runtime that looks like this:

var Size;
(function (Size) {
    Size[Size["XLarge"] = 0] = "XLarge";
    Size[Size["Large"] = 1] = "Large";
    Size[Size["Medium"] = 2] = "Medium";
    Size[Size["Small"] = 3] = "Small";
})(Size || (Size = {}));

While this structure is a bit hard to follow, it allows for the following runtime behavior:

    console.log(Size.XLarge); // 0
    console.log(Size.Medium); // 2
    console.log(Size[Size.Small]); // 'Small'

Looking at the first two lines, you see the underlying values for these enum choices are numbers. However in the last line, you see it is possible to get the enum selection as a string.

If you define a property that accepts one of the enum values as a string, the type of that property will be string which has no association with the enum from which you would like to get the value.

interface Shirt {
    size: string;
}

// all is right with the world
const shirt: Shirt = {
    size: Size[Size.Large] 
};

// what happened to the enum values??
const invalidShirt: Shirt = {
    size: 'ExtraMedium'
};

TypeScript, however does offer a way to limit string values but this is a separate type definition altogether:

 type SizeString = 'XLarge' | 'Large' | 'Medium' | 'Small';
 const good: SizeString = 'Large'; // compiles
 const bad: SizeString = '' // doesn't compile

Now think back to the multi-choice selections in HTML like the dropdown and radio buttons. The HTML elements define values using attributes which are strings. So wouldn't it be great if you could get the simplicity of an enum but with string values instead of numeric values?

The Code

The SizeSelectComponent demonstrates this use case. Its template contains a select element with several options defined, in this case it displays a range of shirt sizes. Notice the Size variable and the Size type defined. These declarations create the 'string enum' definition.

import { Component, Input } from '@angular/core';

const Size = {
  XLarge: 'xl' as 'xl',
  Large: 'l' as 'l',
  Medium: 'm' as 'm',
  Small: 's' as 's'
}
type Size = (typeof Size)[keyof typeof Size];
export { Size };

@Component({
    moduleId: module.id,
    selector: 'app-size-select',
    template: `
        <select [ngModel]="selectedSize">
            <option value="{{size.XLarge}}">Extra Large</option>
            <option value="{{size.Large}}">Large</option>
            <option value="{{size.Medium}}">Medium</option>
            <option value="{{size.Small}}">Small</option>
        </select>    
    `
})
export class SizeSelectComponent { 
    @Input() selectedSize: Size;

    // Bonus - see how the constants define 
    // the values in the markup above
    size = Size;
}

Furthermore, notice the selectedSize input property for this component. Notice it is of type Size. Now when consuming this component, you can set the selectedSize property using the Size variable properties.

import { Component, OnInit } from '@angular/core';

import { Size } from './app.size-select';

@Component({
  selector: 'my-app',
  template: `<app-size-select [selectedSize]="size"></app-size-select>`,
})
export class AppComponent implements OnInit {
    size: Size;

    ngOnInit() {
        // Looks like an enum, but the value is a string
        this.size = Size.Small;
    }
 }

That's the scenario. It looks very similar to an enum but an enum used in this way would pass an integer value. This provides a string value.

Note: Because this example uses the ngModel directive, be sure to import the FormsModule into the NgModule for these components.

What's Going On?

Take a look at the Size declarations again:

const Size = {
  XLarge: 'xl' as 'xl',
  Large: 'l' as 'l',
  Medium: 'm' as 'm',
  Small: 's' as 's'
}
type Size = (typeof Size)[keyof typeof Size];

The first thing to note is that there are two declarations. The first is the variable declaration which assigns all of the string values to a simple object literal. There is one added compile-time feature here where the properties are defined with their respective string literal type. By adding the as 'xl' type when setting the property XLarge: 'xl' as 'xl', TypeScript performs a compile-time check to prevent overwriting Size.XLarge with a different string value.

The second declaration is the type declaration:

type Size = (typeof Size)[keyof typeof Size];

This declaration is easier to read rewritten into two statements:

type SizeLiteral = typeof Size;
type Size = SizeLiteral[keyof SizeLiteral];

The only thing that has changed is that typeof Size is assigned to its own type called SizeLiteral which is then re-used in the Size type declaration. TypeScript uses the typeof operator to create a type from the Size object definition.

The second line introduces the keyof T operator. TypeScript uses keyof T known as the index type query operator to create a string literal type from the properties of an existing type. For instance keyof SizeLiteral is equivalent to the string literal type 'XLarge' | 'Large' | 'Medium' | 'Small'. This is nice but it isn't going to provide the right type for this scenario, so the next level to this is called the indexed access operator.

Whereas the index type query operator pulls the string literals type from the properties of a given type, the indexed access operator, also represented as T[K], pulls the underlying types of those properties. So remember how you used string literals to define the types of the Size declaration such as XLarge: 'xl' as 'xl'? The Size type is equivalent to the string literal type 'xl' | 'l' | 'm' | 's' because those are the types of the Size type's properties.

Now to return to the initial implementation, SizeLiteral is removed in place of typeof Size and wrapped in parentheses to represent T in the T[K], index type query operator:

type Size = (typeof Size)[keyof typeof Size];

Finally, both the variable and type are exported as Size so that the parent component can consume this interface:

export { Size };

Wrapping Up

You saw how to leverage the new TypeScript 2.1 features to create a type that makes working with string literals much like working with an enum. Don't worry if you don't understand this the first time. There is a lot here so you may consider reading the post again and trying it out yourself. For more information on advanced types in TypeScript, take a look at the documentation.

Do you find this technique useful? In what other scenarios would you consider using this solution? Please leave a note in the comments.

Your First Angular 2, ASP.NET Core Project in Visual Studio Code – Part 4

Angular2, ASP.NET Core, Visual Studio Code, Part 4

Derived from photo by Markus Spiske / raumrot.com, CC-BY

This is part four in a series of posts teaching you how to create your first Angular 2 application with ASP.NET Core in Visual Studio Code. Here is the list of posts:

In this post, you write a 'Hello World' style Angular 2 application. You are introduced to components, NgModules, and template syntax. The walkthrough discusses how to bootstrap an Angular 2 application in the browser and along the way you learn how to better manage your TypeScript files in Visual Studio Code.

This post scratches the very surface of what Angular 2 offers. It's included in the series for completeness and to offer perspective regarding the framework's fundamental building blocks. It is not intended to replace the excellent documentation that the Angular team has produced. So when you get the chance, please take some time and go through the Angular 2 Tour of Heroes Tutorial to learn about the wider breadth of features available in the framework.

Note: If you have been following along with the series prior to this post being published, there is an update to the Part 3 Angular 2 configuration. Please ensure you have the @types/node package installed in your devDependencies. This typings library contains definitions for the module.id API used in this part of the walkthrough.

If you would like to jump straight to the code, it's all on GitHub.

Define the Root Tag

First, add a custom tag to your index.html file. Angular uses the custom tag as an identifier for where in the document to render the application. Any content between the opening and closing tags is replaced during the bootstrap process, so this is a convenient place to add a loading message or indicator. Within the body of your index.html page, add the custom my-app element:

<body>
    <my-app>Loading...</my-app>
</body>

Your First Component

It's time to create your first Angular 2 component. An Angular 2 component is essentially a JavaScript class adorned with the @Component decorator in which you define a template. The class encapsulates the behavior of the component and the template encapsulates the view. Angular does the work to wire the view to the behavior for you.

Components are the building blocks of your application. At the root of your application, is a component containing other components, composed together to form a user interface. This composition is sometimes called a component tree where the root component contains all of the other components in your application and leaf components represent the re-usable widgets of your application with which the user interacts.

Component tree

Create a file within your application's app directory called app.component.ts. Go head and add this component code:

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'my-app',    
    template: 'Hello, Angular 2 World!',
})
export class AppComponent { }

The @Component syntax is a decorator. This code adorns the AppComponent class and adds metadata information about the class. In this case, it denotes that this class is an Angular 2 component.

The import statement obtains the Component decorater from the @angular/core library. This import statement is an example of node module resolution which you setup in part 3 of this series. The Component decorator sets three properties, moduleId, selector, and template.

The moduleId property, when used, is generally set in this fashion. By setting it to module.id, you are supplying the current ECMAScript 2015 module's URL to Angular. This helps the framework load external templates and styles from the same relative location (URL) as the component's scripts. In this example, the component has no external templates or styles and thus setting moduleId is not required. However you may find it's easier to always set moduleId than to keep track of when you need it and when you don't.

Angular uses the selector property as a CSS selector to query a template finding all the elements that should be initialized as this component. For instance, when you use this component in a view, you define it as <my-app></my-app>. This selector is a tag selector, because it targets elements by tag name. But you can use other CSS selector types. If you prefer to define your components in markup by class (.my-app) or by attribute ([my-app]) simply change the selector accordingly.

The template property is the view or markup rendered for the component. In a moment, you will explore the template syntax more. It's important to note that there are two template-related properties available on the Component decorator: template and templateUrl. The templateUrl property points to a separate file containing the markup. For any template more than a couple lines, creating a separate file and using templateUrl is generally easier. You'll find in Visual Studio Code (and probably many other editors) that having the extra HTML syntax highlighting and auto-complete when writing in a dedicated .html file is more productive.

Finally, the Component decorator annotates the component class definition. The Angular 2 Style Guide recommends suffixing class names with the Angular object type which is why this class is named AppComponent but ultimately you have the freedom to decide which naming convention to use. Notice the class is exported using the export keyword so that it can be consumed in other ECMAScript modules as you will soon see. Finally, this component is very simple with no behavior which is why the class definition doesn't have much code yet.

Your First Angular Module

Angular 2 brings forward the concept of Angular modules from Angular 1.x although in quite a different form. In Angular 2, Angular modules are defined with the NgModule decorator. To distinguish from ECMAScript 2015 (ES2015) modules, this post will refer to them as NgModules.

First, what's the difference between an NgModule and an ES2015 module? You can think of ES2015 modules as encapsulated, reusable units of code whereas NgModules are encapsulated, reusable units of the application. NgModules are composed of ES2015 modules making them a sort of higher-order unit. Again coming from the Angular 1.x world, NgModules are very similar conceptually to angular.module. If you are coming from a C#/.NET background, you can almost think of NgModules as namespaces or assemblies.

Angular requires you to have at least one NgModule in your application. For small applications this may be preferable, but as your code base grows adding additional NgModules may help you reason about the application.

For this example, create one NgModule to house and bootstrap your application. In the app directory, create the app.module.ts file and add this code:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';

@NgModule({
    bootstrap: [ AppComponent ],
    imports: [ BrowserModule ],
    declarations: [ AppComponent ],
})
export class AppModule { }

Notice that all the configuration occurs in the NgModule decorator. The bootstrap property is only necessary in this root NgModule. It instructs Angular to examine the existing DOM (parsed from index.html in this case), identifying the elements to replace with the components defined in bootstrap.

The imports property defines any NgModules that are consumed by this NgModule. Being this root NgModule is running in a browser, you must import the BrowserModule. The other NgModules in your application do not need to import that BrowserModule, only the root NgModule.

Finally, you declare all of the Components and Directives that belong to this NgModule in declarations. There are several other important properties with the NgModule decorator so refer to the documentation for more information.

Bootstrap the Application

Angular 2 provides multiple ways to bootstrap your application depending on where it is running (server versus browser) and whether it has been compiled ahead of time (AOT) or not. For this example, use just-in-time (JIT) compilation in the browser with platformBrowserDynamic to bootstrap the AppModule. In your main.ts file, replace the contents with this:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

Save all of your files and start your application. From the terminal (Ctrl+`), type npm start to compile the TypeScript and start your ASP.NET Core application. Then view the application in the browser at http://localhost:5000. You see the message 'Hello, Angular 2 World!'

Hello, Angular 2 World!

Hide Generated TypeScript Files

At this point, you have three TypeScript files in your app directory and also six additional .js and .js.map files generated from the TypeScript build.

TypeScript generated files

Having three of each file name in the explorer pane can make it difficult to find the actual file you need. Furthermore, you don't really need to see the JavaScript and source map files during development. To address this, Visual Studio Code has settings to hide these files in the explorer pane.

There are two options when changing Visual Studio Code settings. The editor allows you to configure settings for your user profile on the development machine. In this way, all of your projects pick up the same settings on that machine but unless you manually back it up, those settings could be lost.

You also have the option to configure settings for your workspace which are easy to save in source control and restored on whichever machine you use. The downside is that they are only applied to that particular project. For this exercise, you are configuring workspace settings.

In the File menu, select Preferences, then click Workspace Settings. This creates a .vscode directory in your application root containing a file named settings.json.

File Menu, Workspace Settings

This file contains a set of matching curly braces. Within those curly braces, begin typing "files.exclude" but press TAB before the closing quote symbol. When you press TAB to trigger autocomplete, the default excluded files and directories are populated.

settings.json auto-complete

To that list of globs, add one to hide the source map files and one to hide the generated JavaScript files. When you are done the configuration looks like this:

{
    "files.exclude": {
        "**/.git": true,
        "**/.svn": true,
        "**/.hg": true,
        "**/.DS_Store": true,
        "**/*.js.map": true,
        "**/*.js": { "when" : "$(basename).ts" }
    }
}

When you save the file, you see that now only the TypeScript files are visible in the explorer pane.

No TypeScript Generated Files

Template Syntax

To wrap up this Angular example, you explore the Angular 2 template syntax. Open up the app.component.ts file to make changes to this component.

First, add a property to the component class called food and set it to your favorite food. Then, replace the template property with a basic template. Your component should look like the following:

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'my-app',    
    template: `
        <input type="text" [value]="food" />
        <p>Sriracha sauce is great with {{ food }}</p>
    `,
})
export class AppComponent { 
    food = 'kielbasa';
}

Go head and refresh your browser to see the changes. Also, ensure the request is not pulling from the browser cache.

Template Syntax Example

The first thing to point out is that the template is defined using the ES2015 template literal string syntax. This syntax uses the back-tick character (grave accent) and supports multi-line string values. TypeScript outputs ES5-compatible strings at compile-time.

Also, you see two examples of Angular-specific template syntax. First, the value attribute of the input is surrounded by square brackets []. This is called a property binding because it binds to the property of an element. In this case, the input's value property is set to the resulting evaluation of the component's food property. This syntax replaces the need for a specialized Angular directive. You are instead targeting the element's native properties, setting the bindings using an abbreviated syntax.

On the second line of the template, you see the double curly braces used {{ }} to bind to the same component property. This is the interpolation syntax. When changes to the component are made, the expression inside the curly braces is evaluated and the resulting value is coerced to a string and inserted into the DOM. This syntax is similar to Angular 1.x, although some of the rules around how expressions are evaluated have changed.

Now, edit the value in the text input. You see that it doesn't update the value in the paragraph. The binding only operates from the component to the view, not the other way around.

value doesn't change

To update the value, you wire-up one of the element's events with a method on your component. Instead of wrapping the event name in square brackets [], you wrap it in parentheses () For this example use the input event and create a method on the component class called foodInput to handle the event. This is called an event binding. Your component code should now look like this:

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'my-app',    
    template: `
        <input type="text" [value]="food" (input)="foodInput($event)"/>
        <p>Sriracha sauce is great with {{ food }}</p>
    `,
})
export class AppComponent { 
    food = 'kielbasa';

    foodInput(event: Event) {
        const target = event.target as HTMLInputElement;
        this.food = target.value;
    }
}

Notice in the template, the $event argument is passed to the foodInput method. This represents the argument that you would receive if you had handled the event in the DOM. Furthermore, look at the typings applied to the method. Both the Event and HTMLInputElement types help you use the API in the method. Ultimately, you take the new value from the event arguments and set it to the food property on the component which in turn updates the binding in the template.

value does change

In the past, you may have used two-way binding in Angular 1.x. Two-way binding is still around but the Angular framework is designed to support uni-directional (one-way) data patterns more easily out of the box. Read more about the template syntax here.

In Summary

You have now built an Angular 2 application sitting on top of ASP.NET Core from scratch. In this post, you learned how to bootstrap your application and learned about some of the fundamental building blocks of Angular 2 including components and NgModules. You also learned how to customize the settings in Visual Studio Code to hide files generated by TypeScript.

In the next post, you smooth out some of the rough edges with the development experience. Wouldn't it be great if your browser automatically updated after every code change? That's what part 5 covers so stay tuned.

How is your Angular 2 development going? If you used Angular 1.x, do you find that the frameworks are concept compatible? In what ways? Let everyone know in the comments.

Get Your TypeScript Definitions from NPM

Get Your TypeScript Definitions from npm

Derived from photo by raumrot.com, CC-BY

If you use TypeScript, you appreciate what the type system provides with IntelliSense and easier refactoring. This experience is quite seemless with code you have written. However, using third-party libraries has left many struggling with complex configurations.

With the advent of ECMAScript 2015 (ES2015) modules and the rise of npm as the defacto JavaScript package manager, configuring your type definitions has gotten more complicated. Not only did you have to ensure that your import statements resolved to the correct scripts but now also the correct type definitions.

Thankfully, the community is standardizing third-party type resolution similar to how it is standardizing on ES2015 modules and npm.

Once Upon a Time

Back in the early days of TypeScript, you used to reference type definitions at the top of the .ts file like this:

/// <reference path="../lib/my-favorite-library.d.ts" />

Also you probably got the type definitions from a dedicated registry somewhere like DefinitelyTyped and maybe even used command line tools to setup and configure these type definitions like tsd or typings.

There were shortcuts and optimizations but ultimately it was a whole other set of configuration and package management to get third party libraries compiling with TypeScript.

Relative Path Module Resolution

While you can still write your comment-based type references, the industry has adopted the ES2015 module standard and so has TypeScript.

By adopting this standard, each JavaScript (or TypeScript) file essentially represents a module with its own scope. Any dependencies that this module requires are imported directly from another module by the module's URL. In the future, browsers will understand how to parse this syntax and load each module as needed from the server. Today, TypeScript transpiles these import statements into a configurable module loader syntax such as AMD, UMD, or CommonJS.

Take for example the fact that your GrooveService takes a dependency on your FunkService. Because you wrote your FunkService in TypeScript, both the scripts and the types are found at the same location.

import { FunkService } from './funk-service';

On the other hand, what if you are using angular in Angular 1.x? Initially, you might think to reference it by relative path:

import * as angular from './node_modules/angular/index.js';

This makes sense because this location is where the scripts reside and the browser will load the correct library. However, TypeScript complains that it can't find angular. Well, it turns out the angular library doesn't include TypeScript definitions in the package. TypeScript tried to find the type definitions at the relative path, but they weren't there and you got the dreaded red squiggle..

Angular IntelliSense Squiggle

Traditionally, this is where tsd or typings came into the picture. These repositories contained packaged type defintions for many popular libraries independent of the library provider. And while these repositories solved a real problem in obtaining type definitions, they weren't easy to setup and configure. After all, who wants to maintain two sets of packages managed by two different tools with two different configurations just for the ability to reference one library using TypeScript?

There's another problem with taking a relative path reference to an npm module, what if your code is distributed as an npm package? If you have spent time reading up on npm dependency resolution, you know that you can't guarantee the location of your dependencies in npm. They may be dropped at different levels of the folder structure and therefore attempts to reference npm modules by relative path are fragile.

Node Module Resolution

In order to solve the npm module resolution problem, the folks at TypeScript decided to just follow the npm model. This solution makes it seamless to reference both the code and the types using the same syntax used in NodeJS.

import * as angular from 'angular';

Given this import statement, NodeJS understands that angular is equivalent to /node_modules/**/angular/index.js. It doesn't know exactly which directory it will be in but it knows how to find it.

As of TypeScript 1.6, the compiler understands how to look for type definitions in the same fashion. TypeScript looks for something roughly equivalent to /node_modules/**/angular/index.d.ts. This is great because it ensures that the code and the types can be found in the same npm package.

But what if the code and the types aren't in the same package? For instance, the Angular 2 libraries ship with type definitions included in the package but Angular 1.x does not. This introduces another great solution from the community. Instead of using tsd or typings for your third-party type defintions, now you can just use npm.

The @types organization is available on the public npm registry for the purpose of hosting third-party type definitions that don't ship with their repective package. So if there is a set of types available independent of the package's publisher, by convention you can find them on npm using @types/package-name. For example to download types for angular, you get them by installing the @types/angular npm package. Furthermore, TypeScript understands how to resolve types from the @types directory out of the box.

To ensure node module resolution for your project, set it in your tsconfig.json file:

{
    "compilerOptions": {        
        "moduleResolution": "node"
    }
}

Note: To read more about how TypeScript handles node module resolution, there are more details in the TypeScript documentation.

Bringing Node Module Resolution to the Browser

This all sounds great – less tooling dependencies, easier configuration. NodeJS runs everywhere – desktop clients, web servers, Linux, MacOS, Windows. But unfortunately, when the web browser tries to load scripts from angular you get a 404 result. The browser doesn't understand how to convert the npm package name into the URL for which the script resides.

This is the place where the node module abstraction breaks down. But think about it for a second. Yes, NodeJS doesn't run in the browser. Yes, you aren't necessarily writing a NodeJS backend. But what NodeJS has done is unify the runtime on which web developers run their client-side tooling. This is the environment on which you do your TypeScript compilation, combining modules, minifying – basically optimizing your code to be consumed by the browser. And so it still makes sense that npm would be the best solution for module resolution of both the scripts and the types.

There are a ton of different ways to prepare your application's npm modules for delivery to the browser and you can bet that there will be more coming in the future. For now, SystemJS is a good place to start. Many deveopers already use this library as a script loader and by adding some additional configuration, it's possible to map npm-based dependencies to a relative URL.

Without any additional configuration, the browser is going to see the import statment for angular and attempt to request /angular from the server. What should really happen is that the browser requests the code from the actual package folder like node_modules/angular/index.js. So using your SystemJS configuration file, add the following:

System.config({        
    packages: {
        '': {
            defaultExtension: 'js',
            map: {
                'angular': './node_modules/angular/index.js'
            }
        }
    }
});

This code configures the root of your application (at the path '') to always add a .js extension if one is not present and to also map the 'angular' path. Now when SystemJS sees the import statement for 'angular', it maps that to a request for node_modules/angular/index.js as defined in the configuration.

Automatic Typings Acquisition

If you've made it this far, you understand how this simplifies the process of acquiring and referencing type definitions. It's become simple enough in fact that the Visual Studio Code team is working to make it automatic in the editor. As of this writing, the feature is released but the team admits it still has rough edges. Even still, you can read more about it in the release notes and start trying it by downloading version 1.7.2 or later.

Keep in mind, the tooling may have drawbacks even when it is complete and it's helpful to know what the tooling is doing underneath the covers. Also, this tooling doesn't address the need for mapping npm package names to URLs in the browser like discussed with SystemJS.

The End

The point here is that node module resolution has become the defacto standard in the web development community and TypeScript is no exception. Now that @types is available on npm, you have one place to get your third-party libraries and type definitions – npm.

The tricky party is still configuring node module resolution for the browser but you see how it's possible to setup SystemJS to handle the mapping. Look for more tooling discussion in future posts.

Are you using ES2015 modules and node module resolution with TypeScript? What is your preferred method of mapping the module names to URLs for the browser?

Using EcmaScript 2015 Modules in TypeScript with SystemJS

TypeScript and SystemJS header

There are two powerful tools for Angular developers in TypeScript and SystemJS. They are both in the Angular 2 quick start and they work just as well with AngularJS 1.x for building maintainable Angular applications.

However, getting started using these tools can be tricky. You need to align both tools' configurations so they work together and having a basic understanding of what happens under the hood can save you hours of confusion and frustration. Once you understand the core concepts and setup the configuration, this workflow helps to make your code more decoupled and maintainable – and maybe even more fun.

This post walks through the basics of using SystemJS and TypeScript in Visual Studio for development. You learn the minimum configuration that matters, how it interacts with the rest of the system, and what to expect when compiling and running your application.

Before You Begin

If you want to skip right to the code, it is all on GitHub. The example is in Visual Studio 2015 (there's a free community edition) and is designed to run with minimal dependencies.

The example project is a component called ListMaker which allows the user to make a list by typing in a textbox and pressing ENTER. This component is made up of two child components AddItemTextbox and List, and includes basic CSS styles. The main script bootstraps the ListMaker component and everything is brought together in index.html.

Application structure

SystemJS is the only library used in the example. Adding dependencies with TypeScript and SystemJS, including Angular, is another level of complexity that is omitted here intentionally. There are enough pieces to understand using SystemJS and TypeScript together so watch for more posts on this topic.

Required Tools and Frameworks

This walkthrough uses the following tools and frameworks:

Visual Studio

The demo is built using Visual Studio 2015 Community. Visual Studio provides a seamless experience to compile and run your application on a development server. It also has first class support for TypeScript.

TypeScript Add-In for Visual Studio

This add-in enables TypeScript support in Visual Studio. The demo uses TypeScript 1.8.36. Install the add-in through Visual Studio's Extensions and Updates manager or from the web.

SystemJS

SystemJS is the JavaScript module loader. It's included in the project on GitHub. You can also obtain it from your favorite JavaScript package manager or as a release download from GitHub.

If you don't use Visual Studio, don't fret. You can port the code to another web project as there are no Visual Studio-specific dependencies. Be sure you know how to setup a development server and TypeScript support in your environment of choice.

Workflow Overview

When using these tools together, your workflow requires a compilation step. The Visual Studio TypeScript add-in automatically handles the compilation when you build or run your project. If you aren't in the habit of using front-end compilation tools such as TypeScript, this may be new to you. Here is an overview:

Diagram of TypeScript with SystemJS workflow

TypeScript Configuration

While the default TypeScript configuration in Visual Studio works, there are a couple options to discuss. First, you should consider adding a tsconfig.json file to your project. By default, you find the TypeScript properties in the Visual Studio project properties, however using a tsconfig.json file has advantages.

Most importantly, the project properties do not show all of the options that are available in tsconfig.json so if at some point you want to set other options, you will be limited by the tooling in the project properties. Also, the tsconfig.json file is a standard configuration for every editor and compiler that supports TypeScript. Therfore if someone joins your team that knows TypeScript, they will know where to look for the configuration no matter which tools they use.

Note: you should install the latest update of Visual Studio 2015. Earlier versions did not properly recognize the tsconfig.json file.

The first related configuration option is compilerOption.module. This option sets which module loader format the TypeScript compiler emits. Because SystemJS understands all of them, it's hard to pick the wrong one. Ultimately your choice here may depend on the other tooling you use for building your JavaScript. For now, choose "system" as it is the native format of the loader but you may find that "amd" provides greater compatibility with other compilation tools.

The other option is compilerOption.target. This option sets which version of the ECMAScript® standard to target when generating JavaScript from your TypeScript files. This value largely depends on which browsers you are targeting. The default value is "es3" which gives you the broadest range of browser support. It's worth noting that should you eventually want to emit native ECMAScript module syntax, this option will need to be set to "es2015".

Your tsconfig.json file should at least have:

Implement ECMAScript 2015 Module Syntax

Take a look at the ListMaker component without the ECMAScript 2015 module syntax:

The first thing to note is that ListMaker is in the global scope as it is meant to be re-used within a given application. The ListMaker implementation, however, is not in global scope and is instead wrapped in an immediately invoked function expression (IIFE). If you look closely, you also see that ListMaker depends on two other constructor functions not defined in this code: AddItemTextBox and List. These constructors are also defined in global scope and furthermore you have to ensure that AddItemTextBox and List are loaded in the browser before trying to create a ListMaker.

The fundamental mechanism for modules are import and export statements. The import statements define the module's dependencies and the export statements define the objects that are available to other modules. Take a look at how the ListMaker code changes when using ECMAScript modules:

The module's dependencies are clearly defined in the import statment and you can easily determine where to obtain the source code for these objects looking at its URL. Also, because module scope is separated from global scope by default, the use of an IIFE is unnecessary leading to less code and more readable code.

Finally notice the export statement and the use of the default keyword. While ECMAScript modules can define multiple exports, only one object is defined as the default export. Similarly, the import statements for this module are explicitly importing the default export from the target modules.

This post doesn't go into all the ways you can write your import and export statements. TypeScript supports various options for writing these definitions so read more about them here.

SystemJS Configuration and Reconciling Import URLs

This is likely the most important part configuring SystemJS and TypeScript to work together. Both SystemJS and TypeScript evaluate the import statements. When TypeScript looks at the import statement, it is looking for a TypeScript file so that the compiler can use the types defined in that file. Using import './Foo', TypeScript looks in the current directory for Foo.ts and then uses the information contained in that file for compilation and type checking.

SystemJS on the other hand looks at import './Foo' and then attempts to make a request to the server for <current-directory>/Foo and without any special configuration, this request will produce a 404 resource not found error. What SystemJS needs to request instead is <current-directory>/Foo.js.

Luckily, SystemJS has a configuration option to append a file extension to requests. You configure this by setting up a specific path in your application using the packages option. In the demo, all of the TypeScript files are in the components folder and SystemJS needs to add a .js extension to these requests.

Create a systemjs.config.js file in your application and add the following configuration:

You can optionally configure this behavior for the whole application by using an empty string '' to set the root of your application as the path. Also remember this configuration only applies to requests made by SystemJS and other requests still require explicit file extensions. There are many useful options in the SystemJS configuration so be sure to read up on its capabilities.

Bootstrap the Application

There is bootstrapping logic for the application in main.ts. The browser requests this file first and the script's purpose is to access the Document Object Model (DOM) and render the root component which in this case is ListMaker. It's important to understand using module loaders that the first module retrieved is the last one to execute its contents. For example, even though main.ts is the entry point of the application, all of the other dependent scripts execute prior to main.ts executing.

Once you determine the main script, you bring the configuration and bootstrapping together on the root HTML page. Reference SystemJS, followed by the SystemJS configuration, and then use the System.import syntax from SystemJS to load the post-compiled version of main, 'scripts/main'. Remember that even though the extension is not present, you have configured SystemJS to add the extension and request scripts/main.js.

Once main.js is loaded, all of the other import statements are evaluated and the target modules are loaded as well to compose the application. This diagram illustrates the dependencies and the order of execution when loading.

Diagram illustrating bootstrap process

At this point, you have configured enough for your application to run. Remember to compile the TypeScript first and all of the modules will run and execute in the correct order. Be sure to check out the complete project for all of the details.

Only the Beginning

This is the first step using ECMAScript 2015 modules with SystemJS and TypeScript and this workflow works quite well as a development workflow. Once you understand the configuration and basics of using the tools, you can get up and running rather quickly.

But you need more. As an Angular developer, you need to bring in third-party libraries and seamlessly integrate with your application code. This is the topic of the next post in this series.

At some point, you will want to bundle these scripts together into one request. You may even want to create several bundles that are loaded dynamically via a route change or a certain screen size. The good news is that by separating your code into modules that have explicit dependency references, you have made this configuration much easier to attain.

Finally, the documentation for both of these tools is well done. Be sure to read the TypeScript documentation and the SystemJS documentation for more information.

Stay tuned for more on this topic. If there is anything else you want to know about these tools or if you also have experience with them, please share in the comments below.