January 2017

Volume 32 Number 1

[The Working Programmer]

How To Be MEAN: Type Script with TypeScript

By Ted Neward | January 2017

Ted NewardWelcome back, MEANers.

As this series on MEAN progressed over the last year and a half, one of the interesting changes to the topic list was to the “A” part of the series’ name: AngularJS made a major switch by formally releasing version 2 of its line, and with that came some deep (and breaking) changes to the framework. One of the most striking changes was the choice to adopt TypeScript as the AngularJS “language of choice” for building AngularJS 2 applications, as opposed to plain, vanilla ECMAScript for the 1.x line.

Many readers are familiar with TypeScript, of course, because it’s the latest language to emerge from the code mines in Redmond. For those not familiar with TypeScript, rest easy—TypeScript is actually similar to the ECMAScript 2015 syntax that you’ve read about earlier in this series, with a bit more by way of type information. Readers wanting a full dive into the language are encouraged to read Peter Vogel’s article, “Understanding TypeScript,” in the January 2015 issue of MSDN Magazine (msdn.com/magazine/dn890374).

However, AngularJS 2 uses a particular subset of features of TypeScript and, furthermore, not all readers are entirely comfortable with the TypeScript syntax yet. In addition, TypeScript has gone through a few changes since Vogel’s article was written (version 2 dropped in late September 2015). Therefore, I want to take a quick pass through the language to make sure we’re on the same page before addressing AngularJS 2.

So, let’s talk some TypeScript.

Adding “Type” to “Script”

Conceptually, TypeScript is a straightforward idea: Take the traditional ECMAScript syntax and add some (optional) type information in the form of type annotations, similar to how F# and other functional languages provide type declarations. The TypeScript compiler (technically called a “transpiler” because it goes source-to-source, producing ECMAScript code out of the process) verifies that all type information is respected and obeyed; but the result is still good old, dynamically typed, browser-friendly JavaScript. In other words, the goal here is to obtain all the benefits of a type-safe language such as C# (reduction of obvious code errors through static verification) without either having to change the underlying JavaScript browser platform (good luck with that!) or build an expensive platform-on-top-of-another-platform. Because one of the core tenets of TypeScript is that “any legal ECMAScript program is also a legal TypeScript program,” adopting TypeScript can be an incremental affair. Take small baby steps, getting cozy with new features only as much as the team feels comfortable in doing so, as opposed to jumping in entirely with new syntax (such as what one might need to do with an entirely new transpiled language such as CoffeeScript, Fantom or ClojureScript).

It’s in that latter spirit that I begin this jaunt into TypeScript. I’ll focus on the features that AngularJS 2 uses the most or most obvious and leave the rest for further exploration down the road.

Installing TypeScript

The first thing to note is that like most Node.js-based packages, TypeScript is an npm package. Thus, you install TypeScript via the usual “npm install” command:

npm install –g typescript

Because TypeScript will install a global command (“tsc”), it’s important to use “-g,” the “global” flag, when installing TypeScript. Take a moment and make sure you’ve fully installed the command-line tool by running “tsc”:

$ tsc --version
Version 2.0.3

Therefore, with TypeScript installed, the next step is to write some TypeScript code.

Modules

The starting point for the discussion is that of TypeScript modules.

Presume that I create a file, person.ts, that’s to contain a component. (The term “component” isn’t one that TypeScript emphasizes, but AngularJS 2 does.) The first step is to create a simple function that can be invoked from another file, so let’s first create that function:

function sayHello(message: string) {
  console.log("Person component says", message);
}

Notice the type annotation to the parameter, ensuring that the single parameter must be a string; this is the bedrock of TypeScript, and ensures that only strings can be passed as parameters. Granted, this function by itself makes a simple component, but complex or simple, it needs to be used to be useful.

So let’s use it: a TypeScript application can use a component by using an import statement, like so:

import { sayHello } from './person';
sayHello("Fred");

In essence, the “import” statement declares that you’re using the element named “sayHello” from the “person” module. (There are other forms of import syntax that you’ll see later.) So, you run the two files, the aforementioned person.ts and this code app.ts, through the tsc compiler:

tsc person.ts app.ts

Unfortunately, TypeScript will complain, stating that person.ts is not a module.

Modules are, in TypeScript lingo, the language construct that provides a “box” around a tightly grouped set of code. As written, person.ts would be easily usable as a module under older JavaScript scenarios; simply defining a function in a file and referencing that file puts the function into the global scope. However, TypeScript requires more explicit syntax—you have to use the export keyword to declare what’s part of the external surface area of the component, like so:

export function sayHello(message: string) {
  console.log("Person component says", message);
}

In TypeScript, any file that has a top-level import or export statement is considered a module, so simply declaring that this function is to be exported implicitly defines all of person.ts to be a module.

Once modified, TypeScript is happy, and two new files, person.js and app.js, rest on the filesystem, waiting to be used.

Adding Class

TypeScript, like the ECMAScript 2015 language on which it’s based, understands the core concept of classes, so it makes sense to define Person as a class to be used, as shown in Figure 1.

Figure 1 A Simple Person Class

export class Person {
  firstName: string;
  lastName: string;
  constructor(fn: string, ln: string) {
    this.firstName = fn;
    this.lastName = ln;
  }
  greet() : string {
    return this.fullName + " says hello!";
  }
  get fullName() : string {
    return this.firstName + " " + this.lastName;
  }
}

This is all relatively easy to figure out, even for those who have never looked at TypeScript before. The export keyword again indicates that this class is for use outside of this module. The fields firstName and lastName use TypeScript annotations to get the compiler to enforce “string-ness,” the method greet returns a string to callers, and the fullName method is declared as a synthetic read-only property accessor, made up of the firstName and lastName fields. Using the Person type in the app.ts file is also straightforward—you just need to import the Person type from the person.ts file and construct one using the keyword new:

import { Person } from './Person';
let ted = new Person("Ted", "Neward");
console.log(ted.greet());

Careful readers will note that the import line has changed—instead of pulling in sayHello, it pulls in the Person type. While it would certainly be possible to list all of the symbols exported in Person between the brackets of the import statement, that would get truly tedious very quickly. So TypeScript provides a wildcard import facility, but because you don’t want all of the module’s exported names to just pollute the global namespace, you need to provide a name under which all of those names will be visible. Using it would change the application code slightly:

import * as PerMod from './Person';
let ted = new PerMod.Person("Ted", "Neward");
console.log(ted.greet());

Obviously, this isn’t production-quality code, because PerMod is a terrible name.

Interfacing in TypeScript

Of course, one popular goal for component-based development (which, remember, AngularJS 2 stresses) is that there should be a strong separation between how users of a component utilize the component and how the component provides that utility—in other words, the “interface vs. implementation” distinction. TypeScript takes a page from its conceptual sibling C# here, providing the ability to declare interfaces—which, like in C#, are promises of behavior that an implementation will provide.

So if the Person component wants to distinguish between different kinds of Persons without requiring any implementation restrictions, it can define Person as an interface, provide several different implementations, and maybe a constructor function to make it easy to construct Persons without having to worry about the details between them, as shown in Figure 2.

Figure 2 Creating a Person

export function createPerson(
  firstName: string, lastName: string, occupation: string) : Person {
  if (occupation == "Programmer")
    return new Programmer(firstName, lastName);
  else if (occupation == "Manager")
    return new Manager(firstName, lastName);
  else
    return new NormalPerson(firstName, lastName);
}
export interface Person {
  firstName: string;
  lastName: string;
  greet() : string;
  fullName: string;
}

Creating a class that implements Person is straightforward, using the implements keyword, as shown in Figure 3.

Figure 3 A Person Implementation

class NormalPerson implements Person {
  firstName: string;
  lastName: string;
  constructor(fn: string, ln: string) {
    this.firstName = fn;
    this.lastName = ln;
  }
  greet() : string {
    return this.fullName + " says hello!";
  }
  get fullName() : string {
    return this.firstName + " " + this.lastName;
  }
}

And, as shown in Figure 4, creating subtypes of NormalPerson (for managers and programmers) is equally straightforward, creating a constructor that will defer to its parent class and then overriding the greet method to return messages appropriate to each occupation.

Figure 4 A Programmer Implementation

class Programmer extends NormalPerson {
  constructor(fn: string, ln: string) {
    super(fn, ln);
  }
  greet() : string {
    return this.fullName + " says Hello, World!";
  }
}
class Manager extends NormalPerson {
  constructor(fn: string, ln: string) {
    super(fn, ln);
  }
  greet() : string {
    return this.fullName + " says let's dialogue about common synergies!";
  }
}

Again, aside from the type descriptors (and the interface declaration itself), this is similar to straight-up ECMAScript 2015 syntax, but thanks to TypeScript type-checking, any attempt to use anything other than a string as the parameters to the constructors will be firmly rejected. Note, however, that the fact the classes aren’t exported means that the client code has no idea what the actual implementation is; all the client knows is that the Person interface defines three properties—firstName, lastName and fullName—and one method—greet—that the client can use.

Decorations

The last obvious feature of TypeScript that requires explanation is decorators, an experimental feature for ECMAScript (and TypeScript, for that matter) that look vaguely like custom attributes, but behave quite differently. Essentially, by using an @-prefixed notation, you can define a function that will be invoked whenever different code constructs are invoked—when a class is constructed, when a method is invoked, when properties are accessed (or modified), or even when parameters are passed as part of a method or function invocation. It’s an obvious attempt to provide some aspect-oriented programming approaches to TypeScript, and AngularJS 2 leverages it pretty heavily.

The classic example for an AOP library is that of logging function calls; you’d like to reuse code that logs to console every time a particular function or method is called, regardless of from where that call comes. This, by definition, is a cross-cutting concern, a chunk of code that defies traditional object-oriented reuse constructs such as inheritance. Using TypeScript, you can write a log decorator, and apply that decorator to the methods that you want to decorate with the logging behavior. That behavior is invoked whenever the decorated method is invoked.

In practical terms, this means that if you’ve written a log decorator, the Person implementation returned can use @log on the greet method, and calls will be logged to console, as shown here:

import log from './log';
// ... Code as before
class Manager extends NormalPerson {
  constructor(fn: string, ln: string) {
    super(fn, ln);
  }
  @log()
  greet() : string {
    return this.fullName + " says let's dialogue about common synergies!";
  }
}

When run, it produces some nice method-level logging:

$ node app.js
Call: greet() => "Ted Neward says Hello, World!"
Ted Neward says Hello, World!
Call: greet() => "Andy Lientz says let's dialogue about common synergies!"
Andy Lientz says let's dialogue about common synergies!
Call: greet() => "Charlotte Neward says hello!"
Charlotte Neward says hello!

The log component itself is a nifty bit of runtime type-mongery, but a bit beyond the scope of this column. It’s included in Figure 5 for your perusal, but I won’t describe how it works here other than to say that TypeScript will effectively inject some code into the right places to make calls to the function that the log decorator returns.

Figure 5 Defining the Logging Annotation

export default function log() {
  return function(target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor)
  {
    // Save a reference to the original method
    var originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      var argsLog = args.map(a => JSON.stringify(a)).join();
      var result = originalMethod.apply(this, args);
      var resultLog = JSON.stringify(result);
      console.log(`Call: ${propertyKey}(${argsLog}) => ${resultLog}`);
      return result;
    }
    // Return edited descriptor instead of overwriting
    // the descriptor by returning a new descriptor
    return descriptor;
  }
}

The TypeScript Web site describes decorators at bit.ly/2fh1lzC, for those who want to know how to create them. Fortunately, knowing how to create your own decorators isn’t required to use AngularJS 2 effectively; however, knowing how to use decorators that already exist is definitely a requirement. For starters, AngularJS 2 uses them for dependency injection, a core staple of “The Angular Way” since its inception.

Wrapping Up

I’ve taken a quick pass through TypeScript, and while it’s definitely not an exhaustive treatment, it’ll get you going once you pick up AngularJS 2 and start hacking on that. Note that some of the features described here will require particular compiler switches; decorators, in particular, will require the compiler switch —experimentalDecorators (or its equivalent in tscconfig.json). Most of the time, however, the Yeoman-generated scaffolding will have the right switches already in place, and AngularJS 2 developers don’t need to worry about them.

Speaking of which, it’s time to start exploring AngularJS—­components and models and views, oh my!—so that’s next up on the docket. Until then, happy coding!


Ted Neward is a Seattle-based polytechnology consultant, speaker and mentor. He has written more than 100 articles, is an F #MVP, has authored and coauthored a dozen books. Reach him at ted@tedneward.com if you’re interested in having him come work with your team, or read his blog at blogs.tedneward.com.

Thanks to the following technical expert for reviewing this article: Shawn Wildermuth


Discuss this article in the MSDN Magazine forum