Brian Love
Angular + TypeScript Developer in Denver, CO

TypeScript 2 + AngularJS

Reading time ~14 minutes

If you want to use TypeScript in your AngularJS (Angular v1) application today but you are not yet ready to move to Angular v2 or v4 (yeah, it’s coming) then keep reading.

Updated Aug 14, 2017

I added a new section, as per request, on setting up a service using AngularJS and TypeScript.

Why?

Good question.

TypeScript (TS) is the primary language for Angular going forward (2.x and 4.x). Even if you are not using Angular version 2 or greater you can still start learning and using TypeScript in your Angular apps today.

Getting Started

The first order of business is to initialize our new project, which will create a new package.json file:

$ npm init

After you have answered all of the prompts you should see a new file named package.json in your project root.

Next, install the TypeScript compiler (tsc):

$ npm install typescript --save-dev

We use the --save-dev flag so that this dev dependency is saved into the package.json file.

TypeScript Definitions

Before we can start using TypeScript we need to install the definition files. Definition files (sometimes referred to as header files in other languages) do not contain any code, rather, they describe the API for a third-party library; whether that is jQuery, React or Angular.

Let’s install the TypeScript definitions for Angular. We are going to be using the latest 1.x version of Angular, which as of the time of this writing is 1.6.1.

$ npm install @types/angular --save

Note this time I have used the --save flag so that the production dependency is saved in the package.json file.

Config tsc

In an effort to keep the setup to a minimum I am not going to be using a task runner (like Grunt) to compile the TypeScript code into ES5 JavaScript. Instead, we’ll just use the tsc command directly.

So, let’s create a tsconfig.json file to configure the TypeScript compiler:

{
  "compilerOptions": {
    "module": "none",
    "target": "es5"
  },
  "files": [
    "script.ts"
  ]
}

We will have a single script.ts file that will be compiled to script.js, which will be included in our HTML template.

To compile our TypeScript from the root of our application execute the following command:

$ node_modules/.bin/tsc

Bootstrapping

With the pesky setup out of the way our first task is to bootstrap our app. While you can certainly use the ngApp directive to bootstrap your app, and there is nothing wrong with that approach, let’s look at bootrapping our app using the imperitive/manual way.

First, here is what our basic index.html template looks like:

<!DOCTYPE html>
<html>

  <head>
    <script data-require="angularjs@1.5.8" data-semver="1.5.8" src="https://opensource.keycdn.com/angularjs/1.5.8/angular.min.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body>
    <heros></heros>
  </body>

</html>

There is not much here. We include Angular and our script.js file. In the body of our html document we have a single <heros></heros> component.

Before we write the TypeScript version let’s look at traditional JavaScript for bootstrapping our app:

var module = angular.module("mySuperAwesomeApp", []);

module.component("heros", {
  template: "My heros:",
  controller: function herosController() {},
  controllerAs: "$ctrl"
});

angular.element(document).ready(function() {
  angular.bootstrap(document, ["mySuperAwesomeApp"]);
});

In our script.js file we first create a new module named mySuperAwesomeApp. Then, we create a new heros component. Finally, we wrap up our bootstrap code in a function that will be invoked when the DOM is ready.

If you rename your script.js file to scripts.ts we should start to immediately get some benefits of using TypeScript.

Though we haven’t changed any code yet our development environment should now know that our module variable is of type angular.IModule. Check out the following screen shot from VS Code showing the IntelliSense for IModule:

IntelliSense for angular.IModule

Components

Components shipped with Angular 1.5. Components are essentially simpler and less-complex directives.

Step 1

The first step to taking advantage of TypeScript in our Angular v1 app is to start to use some ES2015 features, such as template strings. Template strings use the backtick, can be multiline and include string interpolation.

Here is a first step for our simple Angular application that uses TypeScript:

module.component("heros", {
  template: `
    <ul>
      <li ng-repeat="hero in $ctrl.heros">{{ hero.name }}</li>
    </ul>
  `,
  controller: function() {
    this.heros = [
      { id: 11, name: 'Mr. Nice' },
      { id: 12, name: 'Narco' },
      { id: 13, name: 'Bombasto' },
      { id: 14, name: 'Celeritas' },
      { id: 15, name: 'Magneta' },
      { id: 16, name: 'RubberMan' },
      { id: 17, name: 'Dynama' },
      { id: 18, name: 'Dr IQ' },
      { id: 19, name: 'Magma' },
      { id: 20, name: 'Tornado' }
    ];
  },
  controllerAs: "$ctrl"
});

A couple of things to note:

  • First, I am defining my template inline using the template property. The template is an unordered list of heros.
  • Second, I am defining the controller using the controller property. The controller defines the array of heros.
  • Finally, I am using the controllerAs property. My controller will be available using the name $ctrl in my template. This is the default behavior with components but I am showing this here in case you want to use a different value.

While this is a good first step there is more to TypeScript that we can take advantage of in our Angular v1 applications.

Step 2

Here are some further improvements we can make:

  1. Create a new IHero interface.
  2. Store our array of heros in a constant named HEROS that is an array of IHero objects.
  3. Update our controller to use the new HEROS constant.

Let’s take a look at these updates:

interface IHero {
  id: number;
  name: string;
}

const HEROS: IHero[] = [
  { id: 11, name: "Mr. Nice" },
  { id: 12, name: "Narco" },
  { id: 13, name: "Bombasto" },
  { id: 14, name: "Celeritas" },
  { id: 15, name: "Magneta" },
  { id: 16, name: "RubberMan" },
  { id: 17, name: "Dynama" },
  { id: 18, name: "Dr IQ" },
  { id: 19, name: "Magma" },
  { id: 20, name: "Tornado" }
];

module.component("heros", {
  template: `
    <ul>
      <li ng-repeat="hero in $ctrl.heros">{{ hero.name }}</li>
    </ul>
  `,
  controller: function() {
    this.heros = HEROS;
  },
  controllerAs: "$ctrl"
});

Let’s review the changes we made:

  • First, we declared a new interface named IHero that has two properties: id and name. The id will always be a number, and the name will always be a string. Both are required.
  • Next, we moved our array of heros into a constant variable named HEROS. Note the const keyword will make the array read-only. Our HEROS will be an array of IHero objects.
  • Finally, I updated the controller function expression to set the value of this.heros to the constant value of HEROS.

Ok. This is good. But there is more.

Step 3

While our app is starting to look more “TypeScript-y” there are still some more possible improvements:

  1. Create a new class that wraps up our component definition.
  2. Create a new class for our controller.

Let’s take a look:

class HerosComponentController implements ng.IComponentController {

  public heros: IHero[];

  constructor() {}

  public $onInit () {
    this.heros = HEROS;
  }
}

class HerosComponent implements ng.IComponentOptions {

  public controller: ng.Injectable<ng.IControllerConstructor>;
  public controllerAs: string;
  public template: string;

  constructor() {
    this.controller = HerosComponentController;
    this.controllerAs = "$ctrl";
    this.template = `
      <ul>
        <li ng-repeat="hero in $ctrl.heros">{{ hero.name }}</li>
      </ul>
    `;
  }
}

angular
  .module("mySuperAwesomeApp", [])
  .component("heros", new HerosComponent());

angular.element(document).ready(function() {
  angular.bootstrap(document, ["mySuperAwesomeApp"]);
});

This is a bit long, so let’s break it down:

  • First, we define a new class named HerosComponentController that implements the ng.IComponentController interface. This class replaces the arrow function expression.
  • We declare the publicly available property heros that is an array of IHero objects.
  • Within the HerosComponentController we have implemented the $onInit() method that will be invoked by Angular when all of the controllers in our component have been constructed and their bindings initialized. In $onInit() we set the value of the heros property to the value of the constant HEROS.
  • Next, we define a new class named HerosComponent that implements the ng.IComponentOptions interface. This class replaces our inline object that defined the template, controller and controllerAs properties. These are now public properties in the class. The value of each of these properties is set in the constructor function. You can also set the value immediately when defining them.
  • Note that the controller is of type: ng.Injectable<ng.IControllerConstructor>. The value of this is the HerosComponentController class.
  • Finally, note that our call to angular.component() is much shorter. We simply provide the name of the component and then a new instance of the HerosComponent class.

While our app is small now, as we continue to build out our super-awesome app we will continue to benefit from the advantages of using classes in TypeScript.

Directives

Directives are similar to components but they have more power and can be more complex. In most instances components will satisfy your requirements. However, let’s look at defining a directive using TypeScript.

In this example I have created a new file named loading-indicator-bar.ts:

export class LoadingIndicatorBarDirective implements ng.IDirective {

  public controller: any = "LoadingIndicatorBarController";
  public controllerAs: string = "loadingIndicatorBarController";
  public restrict: string = "E";

  /**
   * The link function is responsible for registering DOM listeners as well as updating the DOM.
   *
   * @class LoadingIndicatorBarDirective
   * @method link
   * @param $scope {ng.IScope} The scope for this directive
   * @param $element {ng.IAugmentedJQuery} The JQuery instance members object.
   * @param $attributes {ng.IAttributes} An object containing normalized DOM element attributes.
   * @param loadingIndicatorBarController {LoadingIndicatorBarController} A new instance of the controller.
   */
  public link: ng.IDirectiveLinkFn = (
    scope: ng.IScope,
    element: ng.IAugmentedJQuery,
    attributes: ng.IAttributes,
    loadingIndicatorBarController: LoadingIndicatorBarController
  ) => {
    loadingIndicatorBarController.init(element);
  };

  /**
   * Create the directive.
   *
   * @class LoadingIndicatorBarDirective
   * @method Factory
   * @static
   * @return {ng.IDirectiveFactory} A function to create the directive.
   */
  public static Factory(): ng.IDirectiveFactory {
    return () => new LoadingIndicatorBarDirective();
  }
}

This is an example of a directive that uses a controller to display a loading indicator at the top of a webpage.

My directive is very slim. The way I think of this is that it wires up an element to a controller. Let’s quickly review:

  • I created a class named LoadingIndicatorBarDirective that implements the ng.IDirective interface.
  • I’ve set up some publicly available variables named controller, controllerAs and restrict. This should look very familiar if you are used to working with directives in vanilla-JS.
  • I’ve implemented the link() method that will be invoked by Angular.
  • My link() method receives a new copy of my controller from the DI service and I call the init() method (that we will define shortly).
  • Finally, I have defined a static Factory() method whose return type is ng.IDirectiveFactory. This method returns a new function using an arrow function expression that returns a new LoadingIndicatorBarDirective.

When we want to wire up our LoadingIndicatorBarDirective we import the class and invoke the .directive() method on our module:

import { LoadingIndicatorBarDirective } from "./loading-indicator-bar";

angular.module("mySuperAwesomeApp").directive("loadingIndicatorBar", LoadingIndicatorBarDirective.Factory());

Controllers

Most of the time I find that my controller is either used by a directive or I am using the ui-router for state management in my application. In this example let’s look at creating a new LoadingIndicatorBarController that will go with our LoadingIndicatorBarDirective that we previously created.

export class LoadingIndicatorBarController {

  public static $inject: string[] = ["$timeout"];

  private $element: ng.IAugmentedJQuery;
  private $loading: ng.IAugmentedJQuery;

  /**
   * Create the loading bar controller.
   *
   * @class LoadingIndicatorBarController
   * @param $timeout {ng.ITimeoutService} The $timeout service.
   * @constructor
   */
  constructor(
    private $timeout: ng.ITimeoutService
  ) { }

  /**
   * Initialize the controller.
   *
   * @class LoadingIndicatorBarController
   * @method init
   * @param $element {ng.IAugmentedJQuery} The JQuery instance members.
   * @return {ILoadingIndicatorController} Self for chaining.
   */
  public init($element: ng.IAugmentedJQuery): ILoadingIndicatorController {
    //store reference the $element in this scope
    this.$element = $element;

    //create container element
    var container: ng.IAugmentedJQuery = angular.element("<div class=\"loading-container\">");

    //append loading indicator bar
    this.$loading = angular.element("<div class=\"loading\">");
    container.append(this.$loading);

    //append container
    this.$element.append(container);

    return this;
  }

  /**
   * Hide the loading bar.
   *
   * @class LoadingIndicatorBarController
   * @method hide
   * @return {ILoadingIndicatorController} Self for chaining.
   */
  public hide(): ILoadingIndicatorController {
    this.$loading.css({
      opacity: 0
    });
    this.$timeout(() => {
      this.$element.addClass("ng-hide");
    }, 500);
    return this;
  }

  /**
   * Set the width of the bar.
   *
   * @class LoadingIndicatorBarController
   * @method setWidth
   * @param width {number} The percentage width of the loading indicator bar.
   * @return {ILoadingIndicatorController} Self for chaining.
   */
  public setWidth(width: number): ILoadingIndicatorController {
    this.$loading.css({ width: `${width}%` });
    return this;
  }

  /**
   * Show the loading indicator.
   *
   * @class LoadingIndicatorBarController
   * @method show
   * @return {ILoadingIndicatorController} Self for chaining.
   */
  public show(): ILoadingIndicatorController {
    this.$element.removeClass("ng-hide");
    this.$loading.css({ opacity: 1 });
    return this;
  }
}

There is more going on here then we need to cover in detail. You can generally ignore the hide(), setWidth() and show() methods. These methods enable a developer to toggle the visibility of the loading indicator and to set the width of the loading indicator bar.

Some things to note:

  • I am using the $inject property for annotating the dependencies for our controller. The value(s) are injected into my constructor function. In this instance I am injecting the ng.ITimeoutService singleton that serves as a wrapper for window.setTimeout.
  • I have defined a public init() method that is called from my directive. It expects a single argument that is of type ng.IAugmentedJQuery. This is an element in the DOM that has been augmented by jQuery (or jqlite). It is basically the result of angular.element().

Services

Before we get into working with services in AngularJS, let’s look at how the application might be set up. First, we bootstrap our application:

import { Factories } from "./factories";

angular.element(document).ready(function() {
  //module name
  const MODULE_NAME: string = "com.brianflove";

  //create app module
  let app: ng.IModule = angular.module(MODULE_NAME);

  //create factories
  Factories.build(app);

  //bootstrap the application
  angular.bootstrap(document, [MODULE_NAME], {
    "strictDi": true
  });
});

In the code above I bootstrap a new app. Notice that I am invoking a static build() method in the Factories class. Let’s look at the Factories class:

export class Factories {

  private static COMPANIES: string = "/rest/api/companies/:id.json";

  public static build(app: ng.IModule) {

    app.factory("companiesResource", ["$resource", function($resource: ng.resource.IResourceService) {
      return $resource(Factories.COMPANIES, {
        "id": "@id"
      });
    }]);
  }
}

Let’s review the code above:

  • First, we define a new class named Factories. This class will build all of our services.
  • Next, a static COMPANIES variable is set to the resource URL for a REST API endpoint. In this example this is an endpoint for companies. Note the :id placeholder for the company id value. This service is using AngularJS’s $resource provider for interacting with RESTful data sources.
  • The static build() method is invoked when we bootstrap our app.
  • Within the build method we invoke the factory() method on our app, defining a new injectable service named companiesResource. We use the array notation to inject the $resource service into the factory function, which is just the anonymous function that is the last element in the array. Our factory function returns a new resource provider, specifying the endpoint URL and the id parameter.

You might be asking why there is an at-symbol ( @ ) for the parameter default value. This indicates that the value will be provided via the data object when using the resource.

Now, let’s see this in action in a controller:

import { ICompany } from "../models/company";

export class CompaniesController {

  public static $inject: string[] = ["companiesResource"];

  public company: ICompany;

  constructor(
    private companiesResource: ng.resource.IResourceClass<ICompany>
  ) {}

  getCompany(id: string) {
    this.companiesResource.get({ id: id }).$promise.then((company: ICompany) => {
      this.company = company;
    });
  }
}

I have not included all of the necessary code for this example, such as the model definition, but I think this is a good example of using our companiesResource service.

  • First, we import an interface named ICompany, which is an interface for the model returned from the REST API.
  • Next, we define a new controller class named CompaniesController. Don’t worry about the name of the controller, as you’ll be injecting a service into your own controllers.
  • Next, note that we have a public static $inject property. This property instructs the dependency injector to provide services into the class constructor function. The string values in the array have to match exactly to the string value specified when creating the service via the factory method. Also, note that the values injected into our constructor will be in the same order as specified in the array.
  • In the constructor function we expect to receive the companiesResource service that will be injected into our controller. Note that the type declaration of the service is ng.resource.IResourceClass<ICompany>. This will ensure type safety when we use the service, including the return type when we invoke verb methods.
  • Finally, in the getCompany() method we require the id string argument, and then invoke the get() method on the resource service that was injected. The method has a $promise object that we can use to invoke the then() method when the promise is fulfilled. Using a fat arrow function we define an anonymous function that will be invoked with the company object. We then simply store this into a public property of our class.

Final Thoughts

Here are some thing that I have learned along the way:

  1. The Angular TypeScript definitions are very thorough. I find myself studying the interfaces and jumping from one to the next to understand the structure.
  2. Using TypeScript (or ES2015) classes in our components, controllers and directives has some major benefits.
  3. I’m still learning and find myself refactoring after discovering a better approach. Please let me know if you have any suggestions for improvement or if I am totally messing something up. :)

Plunker

I also created a demo plunker for “mySuperAwesomeApp”. Note that the plunker does not use the TypeScript source, rather I simply pasted in the result of executing tsc.

Brian Love

Hi, I'm Brian. I am interested in TypeScript, Angular and Node.js. I'm married to my best friend Bonnie, I live in Denver and I ski (a lot).