How to write AngularJS controller using TypeScript?
AngularJS framework has many powerful features, one of them is Controllers. In this post, I’ll tell you how to write AngularJS Controller using TypeScript.
A Controller is used to augment the Angular Scope. When a Controller is attached to the DOM via the `ng-controller` directive, Angular will instantiate a new Controller object, using the specified Controller’s function. A new child scope will be created and made available as an injectable parameter to the Controller’s constructor function as `$scope`.
There are two options to attach controller to the view, one is controller as syntax and the other is using `$scope`. If controller as syntax is used then the controller instance will be assigned to a property on the new scope.
To get the typed definition, look at this amazing repository for any popular JavaScript library. These definitions allow us to get any compile time errors and good intellisense support in IDE. I use Visual Studio, Visual Code and both supports TypeScript very well.
As mentioned above, AngularJS will create an instance of a controller whenever it is requested. So, a controller can be defined using a class in TypeScript as we know a class can be instantiated. Let us define a Dashboard controller using controller as syntax in the view. The code below doesn’t using `$scope` service.
interface IDashboardVm {
news: { title: string, description: string };
messageCount: number;
people: Array<any>;
title: string;
getMessageCount: () => ng.IPromise<number>;
getPeople: () => ng.IPromise<Array<any>>;
}
class DashboardController implements IDashboardVm {
static $inject: Array<string> = ['dataservice'];
constructor(private dataservice: app.core.IDataService) {
this.getMessageCount();
this.getPeople();
}
news = {
title: 'News',
description: 'Internal server team is excited about AngularJS, TypeScript & JavaScript'
};
messageCount: number = 0;
people: Array<any> = [];
hubsSummary: Array<any> = [];
title: string = 'Dashboard';
getMessageCount() {
return this.dataservice.getMessageCount().then((data) => {
this.messageCount = data;
return this.messageCount;
});
}
getPeople() {
return this.dataservice.getPeople().then((data) => {
this.people = data;
return this.people;
});
}
}
angular.module('app.dashboard').controller('DashboardController', DashboardController);
To leverage the strong typing feature of TypeScript, it is good to create an interface of all the members and behaviours that will be glued with view. This makes easy to define implementation for a controller and this interface then can be used elsewhere if needed as an abstraction. For this, I’ve created an interface above named `IDashboardVm`.
The controller named `DashboardController` then implements this interface and defines the default state for every member. Look at the `$inject` static member of class which instructs AngularJS DI to inject these dependencies before instantiating current controller. The constructor then defines the arguments in the same order of requested dependencies as when they will be injected in those arguments.
The dependencies mentioned for class is quite straightforward, assume that `dataservice` is a custom AngularJS service that encapsulates all the HTTP calls to the server. As per the interface definition, we then define implementation for the behaviours that internally calls the `dataservice` methods. It uses promises to return the response which is then assigned to the controller members to hold that state.
It is really important to notice the position of registering this controller with Angular’s module API. In the code above, first the class is defined and then this registration is done. If the position is swapped, then Angular will not be able to find the implementation of our controller. This works really well when using a JavaScript constructor function as the function hoisting then plays its role.
Below is a code snippet of how this controller can be used using Angular-UI UI-Router but the concept is same if you’d like to use built-in router module in Angular. Note, this just shows the configuration part that uses `controllerAs` syntax.
config: {
url: '/',
templateUrl: 'app/dashboard/dashboard.html',
controller: 'DashboardController',
controllerAs: 'vm',
title: 'dashboard',
}
If you would like to use `$scope` service then extend the interface above like code snippet below. This makes sure that all the members that `IScope` has is available via our interface. Using this will also require some changes in the controller class implementation as now it needs `$scope` service as dependency. The custom interface type then can be used for `$scope` parameter in the constructor to get strong typing and intellisense support.
interface IDashboardVm extends angular.IScope {
news: { title: string, description: string };
messageCount: number;
people: Array<any>;
title: string;
getMessageCount: () => ng.IPromise<number>;
getPeople: () => ng.IPromise<Array<any>>;
}
If you would like to learn more about how to integrate AngularJS and TypeScript, refer to my AngularJS posts. If you want to learn something specific then get in touch and I’ll write a post about it.
