Safe dependency injection for Angular with Typescript

Angular-Typescript-Tip

Dependency injection is your friend when you want your code to be testable and if you work with Angular you know that this is a fundamental concept. Angular 2 will be developed with TypeScript but if you want type safety you can use TypeScript with Angular 1.x today as well. When it comes to DI with Angular and TypeScript, you can do some pretty nice stuff.

Why TypeScript?

Because you may be one of these guys like me that want to write code in a typed language and you know all the benefits of this approach. Still, you can take advantage of all the huge amount of cool work being done in the Javascript world because TypeScript works out of the box with any library or any amount of Javascript code. In fact, as each Typescript tutorial clarifies, Javascript code is totally valid TypeScript code.

If you combine it with Angular, you have a language that helps you and a framework to make your life easier.

Angular dependency injection

Dependency injection in angular is simple in its basic form. You define your services/factories/controllers/whatever through an Angular module and then you use function parameters to inject these in other places. I’m sure you know what I’m talking about but I’ll add an example anyway.

This looks great and it will work just fine until you attempt to minify your code. The minification process will probably alter the names of the parameters and Angular will have no way of knowing what to inject. Your app will break and your boss will be unhappy because the release is going to be late. No one wants an unhappy boss.

There are different ways to solve this problem but my pick is going to be the $inject definition.

You’ll see how this will work in a minute, but first lets try to make the same controller using Typescript and classes.

Ok, so what’s happening here is that we define two classes, one for our service and one for our controller. Immediately after that we use the same way of mapping them as Angular objects. So far so good, I didn’t do anything to solve our minification problem and I don’t take advantage of any types since I’m using the any type. When the TypeScript compiler parses that, the result you will see is something very similar to what you would be doing if you wrote the Javascript side yourself.

$inject to the rescue

So what is that $inject I’ve mentioned earlier? Simply put, this is an array which contains the names of your injections in the same order as your parameters. If you include that in your controller (or any other Angular component), the Angular compiler will take advantage of that instead of using the parameters in your constructor. The tricky part here is that this property needs to be defined as part of the class and not as part of an instance of the class. In the class based object oriented world, this is called a static. Let’s change our controller then.

You now have a properly minifiable (is this a word?) Angular controller! No matter what changes happen to the parameter names, the injections will work because the names in the array will not be changed during minification. In fact, notice that I have added the correct type in the service parameter and changed its name to demonstrate that can name your parameters any way you want.

Avoiding more issues

Everything works as we want it to, but to be honest I am not a great fan of magic strings. Consider that you might need to use the service in multiple places. What happens if you misspell its name or for some reason you need to change its name after some time? Magic strings can lead to problems in these cases. I know because I’ve spent hours wondering why the app crashed and then I’ve noticed I was missing a letter. So much fun!

So how do you solve this? I guess everyone will have his own preferable solution but what worked for me was the following addition to anything that needs to be injected somewhere.

 

Introducing one more static property that contains the injection’s name solves the problem because now the name is defined only in one place and if it needs to change, you just change it there. Everything else will change automatically and minification is not an issue if you keep your order of parameters correct.

 

One last thing

If you’re reading this, you’re probably exploring TypeScript already and if you do so I would strongly suggest to have a look at the controller as syntax. It will make your classes look awesome and you can get rid of the $scope injection in most of them. It will also make your transition to Angular 2 easier.

  • If you see a benefit replacing "SomeController" with SomeController.IID and static IID = "SomeController"; go for it but it looks forced for what you get from it.

    • It has no benefit when you have the injection in 2 or 3 places. But when you have a service that’s reusable and you need to define the injections in multiple places then it makes a difference because you have the string literal only inside the service.

      • Additionally, if you mess up a name, you’ll know at import time (compile time, or even in a smart IDE), because the import will be broken. Magic strings get interpreted at run time.

  • Nice tip.

    I’ve incorporated it in my app (https://github.com/dsebastien/midnightLightV2/tree/master/app/scripts); it feels better to have some compiler safety in there, although it’s too bad we can’t use some introspection to get the fully qualified class name directly..

    • Yes it would be nice to have some way of getting the class name. Maybe there is a feature request for the compiler that you can vote up.

  • Blake Mumford

    Awesome article, thanks!

  • b091

    Hi,

    Thanks for article.

    I’ve made an example project of using TyopeScript with Angular 1.x, and make some tricks with decorators, and gulp to get rid of things like this: static $inject = [“$scope”,”YourService”];

    You can check it and comment here : https://github.com/b091/ts-skeleton

  • Ed.S.

    Thanks for the article. Makes perfect sense. Any self-respecting dev hates magic strings too!

    I’m facing one problem – in my dev environment, where I am neither bundling nor minifying the code, I am getting the error “Uncaught TypeError: Cannot read property ‘IID’ of undefined” because that js file has not yet been
    loaded. Having to mess with the order of the js files would be a pain. How can I avoid this issue?

    • I think it depends on how your code is compiled. Is it compiled in one file or multiple?

  • Pingback: 3b – Services – Getting Started with AngularJS, TypeScript and ASP.NET Web API | If broken it is, fix it you should()

  • A great article indeed.

    We have recently written a practical TypeScript tutorial where we demonstrate how to write an Angular controller, a directive and a service.

    This will be a good additional read to this article

  • Great article, glad I came across your post.
    I stumbled upon same problems over the year working with angular and decided to finally create wrapper framework to ease my development effort. Its growing as I require new features, works great, no complains so far. Have a look, any comments/ suggestions would be greatly appreciated.

    https://navnathkale.wordpress.com/2016/04/20/angles-of-angular-part-1/
    https://navnathkale.wordpress.com/2016/04/25/angles-of-angularjs-part-2/

    • Things will be a lot simpler in Angular 2 and hopefully no one will need to write their one framework wrappers. 🙂 It’s designed to work with typed languages like TypeScript from the ground up and overcoming the problems you and I have is a lot easier. Give it a look when you have some time. It’s currently on Beta 15 I think.

      Regarding you articles, I think one technique that could benefit your coding is the controller-as syntax. It will make your controllers cleaner since you will not need any scope injections and your bindings will be defined as class properties.

      • Your answer was just what I nedeed. It’s made my day!

  • Gabriel C. Troia

    Interesting approach, although I would argue that this is not dependency injection anymore, but dependency extraction 🙂

    At this point your consumer is tightly coupled with the concrete implementation of its dependency (by accessing its IID), which then invalidates the main benefit of having a D.I. System — being able to swap dependencies at runtime (as long as they adhere to the same type).

    What I’m interested in is finding/building a workaround that allows the DI to remain dynamic at runtime (able to swap dependencies) while maintains type safety. In other words I’d like my compiler to error out when I pass a factory/service name that is valid from angular’s perspective but doesn’t adhere to the type the consumer expects.

    • You make a great point Gabriel!

      If we could have type information at runtime, this would be easy. You could have interfaces as injections and their concrete implementations configured through the angular container.

      Also, named injections would not be necessary and the type would determine what will be injected. So no IID as well.

      I believe that’s how Angular 2 does it. But asking something like that from Angular 1.x may be too much since its not built with types in mind.