Table of contents

< Previous chapterNext chapter >

Last updated .

Dependency injection

I have already touched on the use of dependency injection in Angular in a chapter on services. This chapter will investigate dependency injection and component instantiation in detail.

Services, controllers and other types of components in Angular are javascript functions that are 'injected' into the client components that use them. The term 'injected' just means that an instance of the component is passed to the client in its constructor, rather than being instantiated by the client itself. This moves the responsibility of instantiation and the dependency on any concrete service from within the client to something external to it. This pattern of dependency injection (DI) is central in Angular. To make DI work, there needs to be a place where components are registered and instances created. In Angular, this is the module (strictly the $injector, which the module knows about).

Using dependency injection in a strongly-typed environment such as .Net, would require the client of a component to refer to interfaces rather than concrete classes. That way, one component implementation can be swapped with another without the client caring. In Angular, which is just javascript, there is no type safety and no concept of interfaces. Instead of relying on interfaces, a component can simply be a function. After all, one javascript function can easily be swapped for another as long as they take the same sort of arguments and return the same kind of thing.

When creating a component, such as a service or a controller, the DI principle dictates that its dependencies get passed in its constructor function. This is the responsibility of the Angular injector, which is a singleton object named $injector, which is global to all modules. You are not supposed to use that injector object directly, though. There are several different ways to accomplish injection with different syntax. Using the declaration of a controller as an example, I have worked out that there are four ways of doing it.

For the four examples below, the controller will depend on the $log service (a built-in Angular component) and imagine a module named "app" has already been created:

One way of doing it:
function MyController($log) { $log.log("In MyController constructor"); } app.controller("myController", MyController);

This is simple enough, you simply declare the dependency as an argument. Angular is able to read the argument names by calling the javascript toString method on the MyController function. And since a service of the name $log has already been registered with Angular (it is built-in), it can be found and passed as an argument in the constructor. This forces you to use the name $log for the parameter, though ( which isn't necessarily bad), but worse, it doesn't work with minification. That is because minification changes the parameter names.

Another way of doing it:
function MyController($log) { $log.log("In MyController constructor"); } app.controller("myController", MyController, ['$log']);

Now, the dependency is declared explicitly, in that dependencies are passed as an array of the names of components. That is probably better, but it still won't work with minification (try changing the name of the parameter $log).

Yet another way of doing it:
function MyController(logService) { logService.log("In MyController constructor"); } MyController['$inject'] = ['$log']; app.controller("myController", MyController);

This is much better. It will work with minification, and you can name the parameter whatever you want. In the first two examplea, the array of dependencies is actually assigned to a $inject property of the function (remember that a javascript function is just an object). This is all done behind the scenes by Angular. In the current example (the third variation), the $inject property is set explicitly. The $inject array is then used by the Angular machinery to inject the dependencies when calling the constructor function.

The optimal way of doing it:
function MyController(logService) { logService.log("In MyController constructor"); } app.controller("myController", ['$log', MyController]);

Using the $inject property works fine, but feels clumsy and inconvenient. The above way of doing it is more compact, but as effective. With this scheme, the dependencies are again passed as an array of names, but the last array element is the constructor function itself. The dependencies must be declare in the same order as they are listed in the component constructor, or it won't work. But at least you can spell the parameter names as you like.

The two first examples listed above are using what is termed implicit annotation, "annotation" being the process of declaring the dependencies of a component. Since implicit annotation has its drawbacks, you might want to disable it altogether. This can be done by setting a ng-strict-di directive when defining the module:

<div ng-app="app" ng-strict-di>

With that, an error will be thrown if implicit annotation is used.

< Previous chapterNext : Structuring the app with modules >


2016 by Niels Hede Pedersen Linked in