Proper Dependency Injection in Your AngularJS TypeScript Apps


I’ve witnessed quite a lot of legacy AngularJS TypeScript code (yes, including my very own) where dependency injection is done in an impractical way. The most common way of doing dependency injection, is by manually injecting the dependencies while loading your AngularJS components and their accommodating TypeScript classes. What this basically means is that, while injecting a dependency, you have to do this three times (!). Not only that, all three times have to be similar in order for the injection (string matching) to work. Consider the following example:

angular.module("myApp", []).controller("MyController", ["$scope", "MyService", ($scope, MyService)
    => new Application.Controllers.MyController($scope, MyService)]);
module Application.Controllers {
 
    import Services = Application.Services;
 
    export class MyController {
 
        scope: any;
        myService: Services.IMyService;
         
        constructor($scope: ng.IScope, myService: Services.IMyService) {
            this.scope = $scope;
            this.myService = myService;
        }
    }
}

MyController is a class that takes in two dependencies - $scope and MyService. However, in the first code block, the injection itself is written three times (lines 1 and 2). Not only does this result in a maintenance hell, it greatly affects the readability of the code.

So, how do we solve this issue? Simple, we use AngularJS’ $inject property to do the injection in our TypeScript class. In MyController, we statically use $inject and define the dependencies like so (line 10):

module Application.Controllers {
 
    import Services = Application.Services;
 
    export class MyController {
 
        scope: any;
        myService: Services.IMyService;
        
        static $inject = ["$scope", "MyService"];

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

And then simply change the wiring like so:

angular.module("myApp", []).controller("MyController", Application.Controllers.MyController);

Notice how elegant the wiring looks? No explicit injection that we need to worry about, all we need is to inspect the class itself to find the dependencies.