Converting an Angular app to TypeScript – part 1

Introduction

Context

In this post, I will try to set the basics on how to convert an Angular 1 application to TypeScript. Converting applications to TypeScript/ES6 is a first step towards migrating applications to Angular 2.

I build all my front-end apps using TypeScript, as I think it has a high added-value, and makes my life way easier. I actually know TypeScript better than native JavaScript.

This post uses my personal opinions and patterns (if I may use that word). I hope you enjoy reading it; feel free to ask me any question you might have.

As I am focusing on converting an app and not building an app from scratch, I will use John Papa‘s HotTowel generator as the basis for this application. I will therefore keep the spirit and structure of the application, the focus being to move the app to TypeScript.

The interest of this generator is that it is well structured and already comprises a well-established build process.

Code for the completed tutorial can be found on GitHub.

Pre-requisite

If you are here, I guess you are familiar with Angular. I will assume that you know what TypeScript is, but not much more than that. I will also take it that you know what yeoman is and how it works, as well as npm. Finally, some experience with build processes and gulp is advised.

Setting things up

Generator (optional)

This first step is required only if you wish to follow the article based on the same application. If you have an application of your own, you can skip this part.

To create a scaffolded application, yeoman is required. To install yeoman, open a command prompt and type

npm install -g yo

This will make yeoman available on your machine. Next, install the hottowel generator via

npm install -g generator-hottowel

Finally, create a directory, go to that directory and type

yo hottowel myApplicationName

Notes:

  • the generator might get stuck due to multiple Angular versions posible. If it does, just hit ctrl+c to stop it and type bower install
  • the HotTowel generator works with Angular 1.3. Make sure to use 1.3 when installing dependencies

Build process

As TypeScript is a superset to JavaScript, it requires compilation before being handed to the browser. Therefore, a build process is required (or you can compile files manually, but why when it can be automated). I will not explain how to set up gulp or a build process, just how to add a few steps to get our TypeScript files compiled.

To compile our TypeScript files, we will need two new modules: typescript and gulp-typescript. To install them, simply type

npm install --save typescript gulp-typescript

After installing those two modules, tasks need to be configured to build files.

First, we will add references to our TypeScript files to the config file.

In the gulp.config.js file, after temp, add

ts: [
clientApp + '**/*.ts'
],

As you might notice, all entries are sorted alphabetically in this file, to make it easier to maintain. We will also need a second entry for the clientApp folder to be available. After client, add

clientApp: clientApp,

Now that we have access to our files, we can configure our build tasks.

Under the styles task, add the following content:

gulp.task('typescript', function () {
return gulp.src(config.ts)
.pipe($.typescript({
target:'es5'
}))
.js
.pipe(gulp.dest(config.clientApp));
});

This configures gulp-typescript to compile our ts files down to ES5, export only js and output files to clientApp. This will basically tell typescript to compile our client files in their original destination folder, keeping the original file organization. The TypeScript compiler can output ES3, ES5 or ES6 code. This makes the path to the future pretty easy.

As TypeScript relies on types, besides js files, the TypeScript compiler can generate declaration files that contain declaration of all the types you define to make these usable in other projects. We will see declaration types in action later.

This new task needs to be executed whenever the TypeScript files change. We therefore need to add these to the BrowserSync watch task. in the function startBrowserSync, after the watch on less files, add

gulp.watch([config.ts], ['typescript'])
.on('change', changeEvent);

From now on, when BrowserSync will be launched, it will watch all our ts files and reload when any of these changes.

Converting a controller

Using types

Now that everything is ready, let’s convert a controller, e.g. src/client/admin/admin.controller.js. The first step is to rename the file to admin.controller.ts. And boom, you have created your first TypeScript file. That’s one of the great things about TypeScript: any JavaScript is valid TypeScript. Let’s open that file. Depending on your editor (I use Visual Studio Code), you might see that you have some red-underlined variables, namely angular. This is basically TypeScript telling you: hey, you have some variables that I do not know about. To fix this, we will need to add declaration files. As mentioned previously, declaration files contain definition of types to be used in your application. Don’t worry, you do not have to define the angular types yourself. There is actually a huge list of definitions for all the common libraries available at DefinitelyTyped. To add those librearies to your project, you will need tsd. To install tsd, go to the command prompt as administrator and type

npm install -g tsd

From now on, you can install definition files to your application. In a command prompt, go to your application folder and type

tsd init

This will create a tsd.json file allowing you to save the defintion files you downloaded (same kind of thing as package.json or bower.json). Now, let’s add angular definitions to our app:

tsd install --save angular

This will create a folder named typings that will contain all the definition files you downloaded. this will contain a sub-folder per library (e.g. angular), with one file in it (e.g. angular.d.ts).

Now that we have our angular definitions, we have to add a reference to the file in our admin.controller.ts.

To do that, open admin.controller.ts, and at the top of the file, add

///

This will tell TypeScript to look for variables and types in this file. Now the error on the angular variable should disappear.

Creating a module

We are now ready to start the conversion. The first step I will take is to create a module.

Below the reference to angular.d.ts, simply type

module app.controllers{
}

This syntax creates encapsulation and namespacing in you TypeScript application. The syntax above will simply create IIFEs (Immediately Invoked Function Expression), that will help contain the code for your controller in a confined space and avoid polluting the global context. Using modules, your code can be properly encapsulated and clean, without having to write all the IIFE code yourself. Neat, isn’t it?

Creating an interface

Next, let’s create an interface for our AdminController. This interface will expose the different properties and methods the controller actually exposes. In our module, let’s create the interface

export interface IAdminController{
title: string;
activate: () => void;
}

Notice the export in front of the interface. This export allows to access the interface outside the current file. We now know exactly what the AdminController exposes and what can be used in the related view.

Creating a class

We will now need to implement that interface in a TypeScript class. Under the interface freshly created, add:

export class AdminController implements IAdminController {

}

This simply creates a function that allows the creation of a new controller. However, the class does not yet implement the interface. Let’s do that next:

export class AdminController implements IAdminController {
title: string;
activate(): void {
}
}

This way, your editor and the TypeScript compiler should be happy. But this does not yet replace all the logic of the initial code. For the next step, let’s inject the required dependencies (logger in our case):

export class AdminController implements IAdminController {
title: string;
static $inject = ['logger'];
/* @ngInject */
constructor(private logger: any) {
}
activate(): void {
}
}

To inject our dependencies, two operations were required:

  • adding a constructor to the controller, receiving the dependency as a parameter. The parameter is marked as private so that it is available throughout the whole class,
  • adding an injection array via the static $inject property, that lists the dependencies required by the constructor.

It should be noted that the type of the logger argument is any. This tells TypeScript that the logger type is a JavaScript object with unknown properties. We will type the logger when we convert the logger service to TypeScript.

Now we can implement the original behaviours of the controller, namely setting the title, calling activate and implement the activate method:

export class AdminController implements IAdminController {
title: string;

static $inject = ['logger'];
/* @ngInject */
constructor(private logger: any) {
this.init();
}

private init() {
this.title = 'Admin';
this.activate();
}

activate(): void {
this.logger.info('Activated Admin View');
}
}

I like using an init function to handle all my initialization logic. This avoids ending up with a clogged constructor.

Notice the use of the this keyword in front of all the methods and properties. The use of this is a requirement of TypeScript. Be careful to be bitten in the back when handling event callback, because this might not refer to what you think (but this is a different subject).

Registering the controller

Our controller performs all the required operations, the only remaining step is to register our class as an Angular controller:

export class AdminController implements IAdminController {
static controllerId = 'AdminController';
title: string;

static $inject = ['logger'];
/* @ngInject */
constructor(private logger: any) {
this.init();
}

private init() {
this.title = 'Admin';
this.activate();
}

activate(): void {
this.logger.info('Activated Admin View');
}
}

angular
.module('app.admin')
.controller(AdminController.controllerId, AdminController);

I like to make the controller name a static property of the controller itself. This way, I know directly what my controller name is when I open it. I then refer to that static property to register my controller in Angular. Noticeable also is the fact that the function is sent as second parameter, without array of dependencies, as these are handled via the $inject static property of the controller.

If all went well, you should be able to go to your console, type gulp typescript and then gulp serve-dev to see your application working based on your TypeScript controller!

Conclusions

This (quite long) article covered the basics of using TypeScript with an Angular application. We started with setting up our build process, then we renamed our file. We continued with the creation of a module, then an interface, and finally the class creation.

We saw how to reimplement the original logic in a TypeScript class, how to inject dependencies, and then how to register our controller.

I will cover the conversion of a service and a directive in a later article.

Leave a comment

3 Comments

  1. Is there a GitHub project for this? I am trying to do this “This will basically tell typescript to compile our client files in their original destination folder,” but keep failing and would love to see your code as an example.

    Like

    Reply
  1. Converting an Angular app to TypeScript – part 2 | Wandering in the community

Leave a comment