Skip to content

Instantly share code, notes, and snippets.

@esfand
Last active September 30, 2022 12:37
Show Gist options
  • Save esfand/9569523 to your computer and use it in GitHub Desktop.
Save esfand/9569523 to your computer and use it in GitHub Desktop.
AngularJS with TypeScript

AngularJS with TypeScript

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", ($scope)
    => new Application.Controllers.MyController($scope)]);

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 = Customer.CreateCustomer.Services;

    export class MyController {

        scope: any;
        customerService: Services.ICustomerService;
        data: any;

        constructor($scope: ng.IScope, customerService: Services.ICustomerService) {
            this.scope = $scope;
            this.customerService = customerService;
            this.data = [];
        }

        private GetAll() {
            this.customerService.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();
        }

        CreateDirective() {
            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 = Customer.CreateCustomer.Services;

    export class MyController {

        scope: any;

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

        GetAll() {
            this.scope.customerService.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.

@rexprograms
Copy link

You are cluttering up the global scope by declaring an appModule variable. Angular provides both a module setter and getter which is considered best-practice by ng-book and others. It is used like this:

angular.module('myModule',[]);
angular.module('myModule).directive(...);

You can also just chain all of that stuff together, but in a large app you wouldn't be putting everything into one file making the "getter" syntax make even more sense.

See the section on "getters" from the following styleguide for reference/further reading: http://www.codestyle.co/Guidelines/angularjs

@yangmillstheory
Copy link

@emuls are you talking about the TypeScript module declaration?
@emuls +1

What do you think about the TypeScript module declaration?

I don't understand why people do this with Angular, as it provides it's own module system out of the box; it's not necessary to layer your own (which won't support dependency injection) on top of it.

@nbering
Copy link

nbering commented Feb 16, 2016

In your "Bootstrapper" section, there is absolutely no need to wrap the controller classes in an arrow function and then new one up. Classes compile down to a JavaScript constructor function which is exactly what the module's directive function wants you to give it. Also, using $inject syntax is way nicer than separating your dependency injection annotations from your actual code. This can be done with a static property on your class like this:

class MyController
{
    static $inject = ['$http', '$scope'];

    constructor($http, $scope)
    {
        // constructor logic
    }
}

Where you will want to use Arrow Functions with Angular and Typescript is promise callbacks. You normally lose your lexical scope when you run functions asynchronously, but arrow functions maintain your this variable, just like you'd do with var self = this; in vanilla javascript. There are still occasionally times when var self = this; is preferable, but that's another matter.

@asoseil
Copy link

asoseil commented May 11, 2016

how can i use a component instead of a directive?

@alsocalledchris
Copy link

Nice. This seems to work OK when the page renders and scope is available but during my testing if you have something like a ng-click referencing a function on the controller which is clicked after the page is rendered there are errors around scope being undefined (using the IE8 friendly approach and not using aliases - is OK when using aliases). Has anyone else come across this? TIA

@aplocher
Copy link

Shouldn't MyService be implementing IMyService?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment