The road to Angular 2 - Types, as in TypeScript

You may have heard that Angular 2 apps can be written in ES5, ES6 (see our posts on the matter) or TypeScript. And you may be wondering what TypeScript is, and what it brings to the table.

JavaScript is dynamically typed. That means you can do things like:

let pony = 'Rainbow Dash';
pony = 2;

And it works. That’s great for all sort of things, as you can pass pretty much any object to a function and it works, as long as the object has the properties the function needs:

let pony = { name: 'Rainbow Dash', color: 'blue' };
let horse = { speed: 40, color: 'black' };
let printColor = animal => console.log(animal.color);

This dynamic nature allows wonderful things but it is also a pain for a few others compared to more statically typed languages. The most obvious might be when you call an unknown function in JS from an other API, you pretty much have to read the doc (or worse the function code) to know what the parameter should look like. Look at our previous example, the method printColor needs a parameter with a color property. That can be hard to guess, and of course it is much worse in day to day work, where we use various libraries and services developed by fellow developers.

One of Ninja Squad’s co-founders is often complaining about the lack of types in JS, and says he can’t be as productive and produce as good code as he would in a more statically typed environment. And he is not entirely wrong, even if he is sometimes ranting for sheer pleasure too! Without type information, IDEs have no real clue if you’re doing something wrong, and tools can’t help you find bugs in your code. Of course, we have tests in our apps, and Angular has always been keen on making testing easy, but it’s nearly impossible to have a perfect test coverage.

That leads to the maintainability topic. JS code can become hard to maintain, despite tests and documentation. Refactoring a huge JS app is not something done easily, compared to what could be done in other statically typed languages. Maintainability is a very important topic, and types help humans and tools to avoid mistakes when writing and maintaining code. Google has always been keen to push new solutions in that direction: it’s easy to understand as they have among the biggest web apps of the world, with GMail, Google apps, Maps… So they have tried several approaches to front-end maintainability: GWT, Google Closure, Dart… All trying to help writing big webapps.

For Angular 2, the Google team wanted to help us writing better JS, by adding some type information to our code. It’s not a very new concept in JS, it was even the subject of the ECMAScript 4 specification, which has been later abandoned. At first they announced AtScript, as a superset of ES6 with annotations (types annotations and another kind I’ll talk about later). They also announced the support of TypeScript, the Microsoft language, with additional type annotations. And then, a few months later, the TypeScript team announced that they had worked closely with the Google team, and the new version of the language (1.5) would have all the shiny new things AtScript had. And the Google team announced that AtScript was officially dropped, and TypeScript was the new top notch way to write Angular 2 apps!

Enters TypeScript

I think this was a smart move for several reasons. For one, no one really wants to learn another language extension. And TypeScript was already there, with an active community and ecosystem. I never really used it before Angular 2, but I heard good things on it, from various people. TypeScript is a Microsoft project. But it’s not the Microsoft you have in mind, from the Balmer and Gates years. It’s the Microsoft of the Nadella era, the one opening to its community, and, well, open-source. Google knows this, and it’s far better for them to contribute to an existing project, rather than have the burden to maintain their own. And the TypeScript framework will gain a huge popularity boost: win-win, as your manager would say.

But the main reason to bet on TypeScript is the type system it offers. It’s an optional type system that helps without getting in the way. In fact after coding some time with it, I’ve forgotten about it: you can do Angular 2 apps using it just for some parts where it really helps (more on that in a second) and pretty much ignore it everywhere else and write plain JS (ES6 in my case). But I do like what they have done, and we will have a look at what TypeScript offers in the next post. At the end, you’ll have enough understanding to read any Angular 2 code, and you’ll be able to choose if you want to use it, or not, or a little, in your apps.

You may be wondering: why use typed code in Angular 2 apps? Let’s take an example. Angular 1 and 2 have been built around a powerful concept named “dependency injection”. You might already know it, as it is a common design pattern, used in several frameworks for different languages, and, as I said, already used in AngularJS 1.x.

A practical example with DI

To sum up what dependency injection is, think about a component of the app, let’s say RaceList, needing to access the races list that the service RaceService can give. You would write RaceList like this:

class RaceList {
  constructor() {
    this.raceService = new RaceService();
    // let's say that list() returns a promise
    this.raceService.list()
    // we store the races returned into a member of `RaceList`
      .then(races => this.races = races);
      // arrow functions, FTW!
  }
}

But it has several flaws. One of them is the testability: it is now very hard to replace the raceService by a fake (mock) one, to test our component.

If we use the Dependency Injection (DI) pattern, we delegate the creation of the RaceService to the framework, and we simply ask for an instance. The framework is now in charge of the creation of the dependency, and, well, injects it:

class RaceList {
  constructor(raceService) {
    this.raceService = raceService;
    this.raceService.list()
      .then(races => this.races = races);
  }
}

Now, when we test this class, we can easily pass a fake service to the constructor:

let fakeService = {
  list: () => {
    // returns a fake list
  }
};
let raceList = new RaceList(fakeService);
// now we are sure that the race list
// is the one we want for the test

But how does the framework know what to inject in the constructor? Good question! AngularJS 1.x relied on the parameter’s names, but it has a severe limitation, because minification of your code would change the param name… You could use the array syntax to fix this, or add a metadata to the class:

RaceList.inject = ['RaceService'];

We had to add some metadata for the framework to understand what classes needed to be injected with. And that’s exactly what type annotations give: a metadata giving the framework a hint it needs to do the right injection. In Angular 2, using TypeScript, we can write our RaceList component like:

class RaceList {
  raceService: RaceService;
  races: Array<string>;

  constructor(raceService: RaceService) {
    // the interesting part is `: RaceService`
    this.raceService = raceService;
    this.raceService.list()
      .then(races => this.races = races);
  }
}

Now the injection can be done! You don’t have to use TypeScript in Angular 2, but clearly part of your code will be more elegant if you do. You can always do the same thing in plain ES6 or ES5, but you will have to manually add the metadata in another way (we’ll come back on this in more details).

That’s why we’re going to spend a few time learning TypeScript (TS) in the following posts. Angular 2 is clearly built to leverage ES6 and TS 1.5+, so we will have the easiest time writing our apps using it. And the Angular team really hopes to submit the type system to the standard committee, so maybe one day we’ll have types in JS, and all this will be usual.

Stay tune for the next episode on TypeScript features and register to our mailing-list if you want to be informed on our ebook progress!



blog comments powered by Disqus