AngularJS via TypeScript – Controllers

This is the 1st in a series of articles about developing for AngularJS using TypeScript. The articles in the series are:

  1. Controllers (this article)
  2. Controllers – Part 2
  3. Modules
  4. Services
  5. Services and $http
Introduction

As I mentioned in my previous post I am currently using AngularJS a lot at the moment and am trying to write the majority of my JavaScript code using TypeScript. To be able to do this effectively I wanted to put together a set of base classes in TypeScript that I could use in my AngularJS projects.

A lot of simple AngularJS demos on the web seem to start by just creating a controller so I’m going to do the same here. Let’s take a simple example of an AngularJS controller written in JavaScript:

function MyController( $scope )
{
  $scope.message = { title: "Hello World!!" };
}

This could then be saved in a file called controller.js and then used in a web page like the following:

<!DOCTYPE html>
<html>
<head>
 <title>AngularJS in TypeScript</title>
 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.min.js"></script>
 <script type="text/javascript" src="controller.js"></script>
</head>
<body>

<div ng-app="" ng-controller="MyController">
 <h1>{{message.title}}</h1>
</div>

</body>
</html>

Which would then output something like the following:

Hello World!!

As you can see it’s a very simple and trivial AngularJS example and one that shouldn’t be difficult to write using TypeScript. The first thing we have to do is delete our controller.js file and create a new file, called controller.ts. We can then populate controller.ts with the following code:

class MyController
{
  constructor( $scope )
  {
    $scope.message = { title: "Hello World!!" };
  }
}

When compiled this would produce our controller.js with the following JavaScript:

var MyController = (function () {
    function MyController($scope) {
        $scope.message = { title: "Hello World!!" };
    }
    return MyController;
})();

and if you refresh the page you see the same Hello World!! text. Wow! I bet you’re amazed with that eh? Nope?

Adding Types

All we’ve done is prove we can write some TypeScript which creates a simple controller but doesn’t really show why we’d want to use TypeScript in the first place. One of the useful features when writing TypeScript is the code-completion help that you get but to get proper benefit from that you have to be explicit with types. In the example above we passed a $scope property to the constructor but didn’t specify a type so would get no help with the methods and properties available to us.

If we now change the code in controller.ts to be as follows:

interface IMyMessage
{
  title: string;
}

interface IMyScope extends ng.IScope
{
  message: IMyMessage;
}

class MyController
{
  constructor( $scope: IMyScope )
  {
    $scope.message = { title: "Hello World!!" };
  }
}

You can see above that our $scope parameter in the constructor has an explicit type, IMyScope and that is defined to inherit from ng.IScope (this is an interface representing the properties and methods available on an AngularJS $scope). The IMyScope type has a message property which itself is of type IMyMessage, where IMyMessage has a string property called title. If you now recompile controller.ts it will produce exactly the same JavaScript in controller.js as it did before.

That’s because all we added were type definitions which are not relevant to the JavaScript output but are useful for us when editing the code. So we’re writing extra code just so we can get some better code-completion then? So far, yes, but if we extend it a bit further we will begin to see other benefits.

Triggering Events

If we now change our web page and add a button below the h1 element:

...
<div ng-app="" ng-controller="MyController">
  <h1>{{message.title}}</h1>
  <button ng-click="clickMe()">Do It!</button>
</div>
...

The standard thing to do next would be to change the controller so that it adds a method called clickMe to the scope, like the following:

...
class MyController
{
  constructor( $scope: IMyScope )
  {
    $scope.message = { title: "Hello World!!" };

    $scope.clickMe = function()
    {
      alert( "Hello" );
    };
  }
}

Then clicking on the button will show an alert dialog with the Hello text in it.

Personally I’m not a fan of these inline functions at the best of times but even more so when writing in TypeScript. Instead I’d like to write a proper method and have that called by the click event. Since the $scope is provided to my controller through the AngularJS dependency injection pattern it’s not a trivial matter to add a clickMe method to it. It is, however, easy to add a new method to my controller and I could store a reference to my controller on the scope so that the view/html can get access to the method.

To do this I could change my controller to look like the following:

...
class MyController
{
  constructor( $scope: IMyScope )
  {
    $scope.events  = this;
    $scope.message = { title: "Hello World!!" };
  }

  clickMe()
  {
    alert( "Hello" );
  }
}

Then the button in my html code could be updated to the following:

...
  <button ng-click="events.clickMe()">Do It!</button>
...
Referencing $scope

Now, what if I wanted to display the message.title property from the $scope in my alert box when the button is clicked? To do this I’d need to store a reference to my $scope so that the clickMe method get to it. This can be done as follows:

...
class MyController
{
  scope: IMyScope;

  constructor( $scope: IMyScope )
  {
    this.scope     = $scope;
    $scope.events  = this;
    $scope.message = { title: "Hello World!!" };
  }

  clickMe()
  {
    alert( this.scope.message.title );
  }
}
Code Reuse and Inheritance

For me the first 2 lines of the constructor seem like common things that I’d like to have on all my controllers but I don’t really want to have to add them every time. This is where TypeScript really comes into it’s own for me – inheritance. I can now write a base controller class like the following:

module KodeYak
{
  export interface IScope extends ng.IScope
  {
    events: Controller;  
  }

  export class Controller
  {
    scope: IScope;

    constructor( $scope: IScope )
    {
      this.scope        = $scope;
      this.scope.events = this;
    }
  }
}

and I can change the MyController class to the following:

module MyTestApp
{
  export interface IMyMessage
  {
    title: string;
  }

  export interface IMyScope extends KodeYak.IScope
  {
    message: IMyMessage;
  }

  export class MyController extends KodeYak.Controller
  {
    scope: IMyScope;

    constructor( $scope: IMyScope )
    {
      super( $scope );
      $scope.message = { title: "Hello World!!" };
    }

    clickMe()
    {
      alert( this.scope.message.title );
    }
  }
}

I added use of TypeScript modules to the code above so as to namespace the code to prevent similar class names from interfering with each other. (This means that the ng-controller tag in the html file should be changed to MyTestApp.MyController)

Conclusion

Hopefully in this post I’ve started to show the merits of using TypeScript to write AngularJS applications and in future posts I intend to expand on this with the intention of putting together a library of TypeScript classes that can be used to make AngularJS application even easier to develop!

3 thoughts on “AngularJS via TypeScript – Controllers

  1. Richard says:

    Hello there and first off thank you for your time spent in doing this tutorial.
    I however got stuck at the part when you added the click event and get this error message:

    “The property ‘clickMe’ does not exist on value of type ‘iMyScope’. ”

    I can easily see what is wrong because my iMyScope looks like this:
    interface iMyScope extends ng.IScope
    {
    message: iMyMessage;
    }

    Is something missing from the tutorial in adding more properties to the interface iMyScope? and in that case could you tell me how i write that clickMe property into the interface.

    Happy for any answer!
    Thank you.

    • KodeYak says:

      You could add clickMe: Function to the interface definition of the scope but that would only resolve any TypeScript errors, it would not implement the function (the implementation is done via an inline function in the constructor of the controller). If you read the article to the end you’ll see that I actually propose adding the clickMe method to the controller instead and adding a reference to the controller on the $scope

Leave a comment