When Two Forces Meet (AngularJS, TypeScript)

AngularJS is largely growing in popularity among front-end developers. According to a JavaScript developer survey conducted in 2013, it was shown that AngularJS was among the top two most used JavaScript frameworks. In a world where there are close to 17 million MVC JavaScript frameworks, this puts AngularJS on top of the game. On the other end of the scale, while not as widely used as AngularJS, TypeScript is slowly becoming the de facto compiler language for writing object oriented JavaScript. So what happens when these two forces meet? A great power emerges! We learned from Uncle Ben (Spider-Man movie) that with great power, comes great responsibility. In this tutorial, I show how these two forces can be combined to produce an ultimate web application.

Capture

Setup

As a .NET developer, I use Visual Studio 2013 and the TypeScript plugin for Visual Studio. This is what I recommend that you use unless you are for some reason against the Windows platform. Then using the NuGet packet manager, you can install the necessary AngularJS core files. In addition to this, make sure to install the AngularJS TypeScript definitely typed files which provides typed AngularJS components that can be used in your TypeScript code.

Bootstrapper

In AngularJS you create an app.js where you load your modules, controllers, factories and directives. You do almost the same thing in TypeScript, the only difference here is that your controllers, factories and directives are represented as TypeScript classes in an app.ts file. So your app.ts can look like this:

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

appModule.controller("MyController", ["$scope", "MyService", ($scope, MyService)
    => new Application.Controllers.MyController($scope, MyService)]);

appModule.factory("MyService", ["$http", "$location", ($http, $location)
    => new Application.Services.MyService($http, $scope)]);

appModule.directive("myDirective", ()
    => new Application.Directives.MyDirective());

Note the usage of lambda, we do this to reserve lexical scope. Always make sure to use this instead of function() in TypeScript. Now that you’ve set up your bootstrapper, in the next sections we’ll look at how the individual AngularJS components are written in TypeScript.

Controller Classes

Controllers are written as classes, so your MyController.ts class can look like this:

module Application.Controllers {

    import Services = Application.Services;

    export class MyController {

        scope: any;
        myService: Services.IMyService;
	    data: any;
		
        constructor($scope: ng.IScope, myService: Services.IMyService) {
            this.scope = $scope;
            this.myService = myService;
	        this.data = [];
        }

        private GetAll() {
            this.myService.GetAll((data) => {
                this.data = data;
            });
        }
	}
}

Factory Classes

Similarly, factories or services are written as classes. So your MyService.ts class can look like this:

module Application.Services {

    export interface IMyService {
        GetAll(successCallback: Function);
    }

    export class MyService {

        http: ng.IHttpService;
        location: ng.ILocationService;

        constructor($http: ng.IHttpService, $location: ng.ILocationService) {
            this.http = $http;
            this.location = $location;
        }

        GetAll(successCallback: Function) {
            this.http.get(this.location.absUrl()).success((data, status) => {
                successCallback(data);
            }).error(error => {
                successCallback(error);
            });
        }
	}
}

Note the interface IMyService here. Always use interfaces to abstract your classes in TypeScript, just as you would usually do in a typed language.

Directive Classes

Directives are also written as classes. So your MyDirective.ts class can look like this:

module Application.Directives {

    export class MyDirective {

        constructor() {
			return this.CreateDirective();
        }

        private CreateDirective():any {
            return {
                restrict: 'E',
                template: '<div>MyDirective</div>
            };
        }
    }
}

Databinding with Alias

Finally, to be able to use your TypeScript classes from your HTML, you need to databind using alias. So your HTML can look like this:

<html data-ng-app="myApp">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
</head>
<body>
	<div data-ng-controller="MyController as mc">
		<div data-ng-repeat="element in mc.data">
			<label>{{element}}</label>
		</div>
		<my-directive></my-directive>
	</div>
</body>
</html>

Databinding without Alias

Unfortunately, databinding with alias does not work in Internet Explorer 8. Neither do directive elements(!). To make it work in IE 8, you need to change your HTML so it looks like this:

<html xmlns:ng="http://angularjs.org" id="ng-app" data-ng-app="myApp">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--[if lte IE 8]>
            <script>
                document.createElement('ng-include');
                document.createElement('ng-pluralize');
                document.createElement('ng-view');
                document.createElement('my-directive');
                document.createElement('ng:include');
                document.createElement('ng:pluralize');
                document.createElement('ng:view');
            </script>
            <script src="libs/es5-shim/es5-shim.js"></script>
            <script src="libs/JSON/json3.js"></script>
        <![endif]-->
</head>
<body>
	<div data-ng-controller="MyController">
		<div data-ng-repeat="element in data">
			<label>{{element}}</label>
		</div>
		<my-directive></my-directive>
	</div>
</body>
</html>

Now if you want to call your TypeScript class methods from your HTML in IE 8 without an alias, then you need to hook your methods (and everything else they use) onto the Angular scope. This can be done like the following, in MyController.ts:

module Application.Controllers {

    import Services = Application.Services;

    export class MyController {

        scope: any;
		
        constructor($scope: ng.IScope, myService: Services.IMyService) {
            this.scope = $scope;
            this.scope.myService = myService;
			this.scope.data = [];
			this.scope.GetAll = this.GetAll;
        }

        GetAll() {
            this.scope.myService.GetAll((data) => {
                this.scope.data = data;
            });
        }
	}
}

Notice how everything is hooked onto scope. That way, databinding is correctly achieved in IE 8, and you are able to call your GetAll() method from the HTML by simply typing GetAll() without alias.

Hope you enjoyed this tutorial! I’m planning on holding a workshop about this and doing TDD at this year’s Norwegian Developers Conference 2014, so make sure to book your tickets! 🙂

Advertisements

14 thoughts on “When Two Forces Meet (AngularJS, TypeScript)

  1. Very nice article! Love the way you use the lamdas with in the factory/controller/directive declarations to solve the DI and minification issue. Will be moving to TypeScript 1.0 to rein in our Angular SPA project to make the JS manageable. Your article gave me a good base from which to start.

  2. Very nice article. I don’t get the proposed way of doing directives to compile, well it seems to compile to js, but won’t build with the error “Return type of constructor signature must be assignable to the instance of the class” – which kinda makes sense too. How did you solve this problem (I’m using VS 2013 update 2)?

    • Thank you, cfsam! That makes sense indeed, but, the code in the article was written when TypeScript was in beta and it was possible to build this.

      To get around the issue today, you need to set the return type of the directive method to “any”. If you do this, then it will build 🙂

      Good luck!

      EDIT: I’ve updated the code in the article.

  3. The part i don’t get is :
    appModule.controller(“MyController”, [“$scope”, ($scope)
    => new Application.Controllers.MyController($scope)]);

    but your controller’s constructor takes 2 arguments:

    constructor($scope: ng.IScope, customerService: Services.ICustomerService)

    How do you wire up the factory created in the module.:

    appModule.factory(“MyService”, [“$http”, “$location”, ($http, $location)
    => new Application.Services.MyService($http, $scope)]);

    with the factory parameter in the controller?

    • Hi John!

      Each code block example is supposed to be independent to provide you the steps needed to create your app.

      That being said, I see that it may have been confusing, so I have now edited the bootstrapper part and injected MyService in the MyController construction. I have also edited the factory part and renamed CustomerService to MyService.

      Hope that clears it up! 🙂

  4. Thanks for the great article.
    In your factory class, you call http.get on location.absUrl() and then store the result in data, expecting data to be an array. But data will not be an array, but instead will contain the text string of the contents of index.html. The contents of the element will then be blank.
    Were you supposed to be doing the http.get of some other URL?

    • Hi John,

      Glad you liked the article!

      Indeed, the idea here was to demonstrate how you can do a GET request from your TypeScript factory to a given URL and update your data field with a success callback. The response data itself depends on what the URL returns. In this case we used the local URL which may not make any sense, but the purpose here was to demonstrate how you can do a GET request to any given URL and handle the response.

      Hope this clarifies it!

  5. hey man,
    thanks for the article.

    how would you pass services into your directives as dependencies? when i try passing them to the constructor and then calling them from the link-function i always get “_this.ServiceName.method is not a function”. printing the service to the console works.

    seems like the service isn’t defined yet when the directive runs. any idea?

  6. appModule.factory(“MyService”, [“$http”, “$location”, ($http, $location)
    => new Application.Services.MyService($http, $scope)]);

    $scope -> $location, no?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s