Tuesday, September 9, 2014

Revisiting AngularJS with TypeScript

Many of my recent blog posts and presentations have focused on pure JavaScript. I believe TypeScript is an incredibly useful tool, especially when developing heavy client apps with large teams. I don’t use it in most examples so they stay relevant to developers who haven’t adopted it. TypeScript is a strong asset for AngularJS apps. I was recently asked about my Angular app structure using TypeScript, so I developed a small example.

In my recent talk about Advanced AngularJS Tips and Tricks, I provided a fairly interactive example using a list of pictures with a selection. The purpose was to demonstrate the controller as syntax and show that you can accomplish what is needed without depending on explicit watchers. You can browse the source; the controllers, etc. are underneath the “01” subfolders.

The first step to developing apps in TypeScript is to pick up any related definition files. I use a repository called definitely typed. There are definitions for most of the common libraries, including Angular. What’s more, you can also install it in your Visual Studio projects using NuGet.

After I have the type definitions in my project, I define an application module. The module encapsulates the functionality and avoids naming collisions. I should give you the caveat that the Google best practices dictate you should always reference your modules by calling angular.module with the module name (after you’ve defined the module by passing in dependencies), rather than trying to capture a global reference. Personally I don’t have an issue retaining the reference when I am already creating a module scope for the app, so I choose to use it.

declare var angular: ng.IAngularStatic;
 
module Application {
    "use strict";
    export var angularApp: ng.IModule =
        angular.module("angularApp", ["ngAnimate"]);
}

The code takes advantage of the definitely typed library to define the angular static type. This gives me IntelliSense/auto-completion and development-time checking of the functional calls I’ll be making. The only real code generated is the scoped reference to the current module contained in the angularApp variable. The generated code looks like this:

var Application;
(function (Application) {
    "use strict";
    Application.angularApp = angular.module(
        "angularApp", ["ngAnimate"]);
})(Application || (Application = {}));

The next logical component to define is the data. For the data I do two things. First, I export an interface to make it easier to deal with the later elsewhere in the app. This will provide discovery and development-time type checking. Second, I create a function to encapsulate the definition before I pass it to Angular. This closure prevents the data from “leaking” to the rest of the app (and potentially colliding or further dirtying the logical namespaces), so after the call the only way to reference it is through the Angular dependency. I truncated the list for the sake of display:

module Application {
 
    "use strict";
    export interface ISunsetData {
        image: string;
        location: string;
        title: string;
    }
    function initData(): void {
        var data: ISunsetData[] = [
            {
                image: "backyard.jpg",
                location: "Woodstock, GA",
                title: "Sunset in Woods"
            }
        ];
        angularApp.value("sunsets", data);
    };

    initData();
}

After the data is defined, I wrap it in a service. The service exposes a filter that controllers can use, and provides a “selected item” used to render the full size picture. Notice that in TypeScript I can expose a contract for the service without exposing the service itself. The class for the service is not exported, so it is only available for registration with Angular. When I reference it, I use the interface.

module Application {
 
    "use strict";
    export interface ISunsetService {
        sunsets: ISunsetData[];
        selected: ISunsetData;
        filter: string;
        select: (sunset: ISunsetData) => void;
    }
    class SunsetService implements ISunsetService {
        constructor(
            public sunsets: ISunsetData[],
            public $log: ng.ILogService) {

            $log.log("Sunsets service created.");
        }
        public selected: ISunsetData;
        public filter: string;
        public select(sunset: ISunsetData): void {
            this.$log.log("Selected sunset "
                + sunset.title);
            this.selected = sunset;
        }
    }
    angularApp.service("sunsetSvc", [
        "sunsets", "$log", SunsetService]);
}

In this example, I register the service using the variable I set up in the main app to reference the module. I also am using the registration-time version of dependency injection since I am registering the class the same time I define it. If I were using a system to define the class and then register it elsewhere, I’d use the static $inject: string[] signature instead so the dependencies are easily discoverable.

I don’t have any need to expose the definition for the controller to the rest of the application, so I simply define the class without exporting it and register it with Angular. I use the exported contracts to bring properties into the constructor. Note how easy TypeScript makes it to define property getters and setters.

module Application {
 
    "use strict";
    class SearchController {
        private _filter:string;
        constructor(
            public $log: ng.ILogService,
            public sunsetSvc: ISunsetService) {
            this._filter = "";
            $log.log("Search controller created.");
        }
        public get filter(): string {
            return this._filter;
        }
        public set filter(val: string) {
            this.sunsetSvc.filter = val;
            this._filter = val;
        }
    }
    angularApp.controller("searchCtrl", ["$log", "sunsetSvc", SearchController]);
}

This is the generated JavaScript for the filter property:

Object.defineProperty(SearchController.prototype,
    "filter", {
    get: function () {
        return this._filter;
    },
    set: function (val) {
        this.sunsetSvc.filter = val;
        this._filter = val;
    },
    enumerable: true,
    configurable: true
});

The list controller is similar. You can view the full source for the converted TypeScript project at this link. It is part of the original project, and only contains the TypeScript source files for simplicity.

As you can see, TypeScript works very well with AngularJS. The definition files help with exploring the API and ensuring it is used correctly at development time. Interfaces help describe structure and it is easy to contain them within defined modules or scopes. Although all of this is possible with pure JavaScript, the TypeScript language not only makes it easier to define these patterns, but also implements them in a consistent way.

Do you use TypeScript with AngularJS or are you considering it? If so, share your thoughts or questions in the comments below!