AngularJS From Beginning: Dependency Injection - Part 11

I am here to continue the discussion around AngularJS. Today, we will discuss about the dependency injection in AngularJS. Also in case you have not had a look at our previous articles of this series, go through the following links:

In this article, I describe the services that AngularJS uses behind the scenes for registering AngularJS components and injecting them to resolve dependencies. These are not features you will use in everyday projects, but they are interesting because they provide some useful insights into the way that AngularJS works behind the scenes and because they are useful for unit testing, which I will discuss in later article.

Registering AngularJS Components

The $provide service is used to register components such as services so that they can be injected in order to satisfy dependencies (the $injector service does the actual injection, as I describe in the “Managing Injection” section later in this article). For the most part, the methods defined by the $provide service are exposed and accessed through the Module type, but there is one specialized method that is not available through Module that offers a useful, albeit niche, feature. Below table lists the methods defined by the $provide service.

Name Descriptions
constant(name, value)Defines a constant value
decorator(name, service)Defines a service decorator, as explained in a moment
factory(name, service)Defines a service, as described in previous article series
provider(name, service)Defines a service, as described in previous article series
service(name, provider)Defines a service, as described in previous article series
value(name, value)Defines a value service, as described in previous article series

The method that is not exposed via the Module type is decorator, which is used to intercept requests for a service in order to provide different or additional functionality.

For demonstrating this, I wrote down the below code,
 
App.js
  1. var testApp = angular.module('TestApp', []);  
  2.   
  3. testApp.config(function ($provide) {  
  4.     $provide.decorator("$log"function ($delegate) {  
  5.         $delegate.originalLog = $delegate.log;  
  6.         $delegate.log = function (message) {  
  7.             $delegate.originalLog("Decorated: " + message);  
  8.         }  
  9.         return $delegate;  
  10.     });  
  11. });  
Index.html
  1. <!DOCTYPE html>  
  2. <html xmlns="http://www.w3.org/1999/xhtml" ng-app="TestApp">  
  3. <head>  
  4.     <title>Angular Injection</title>  
  5.     <script src="angular.js"></script>  
  6.     <script src="app.js"></script>  
  7.     <script src="Index.js"></script>  
  8.       
  9. </head>  
  10. <body ng-controller="indexController">  
  11.     <div class="well">  
  12.         <button class="btn btn-primary" ng-click="handleClick()">Click Me!</button>  
  13.     </div>  
  14. </body>  
  15. </html>  
Index.js
  1. testApp.controller("indexController"function ($scope, $log) {  
  2.     $scope.handleClick = function () {  
  3.         $log.log("Button Clicked");  
  4.     };  
  5. });  
The output of the program is as below -

 
 

Managing Injection

The $injector service is responsible for determining the dependencies that a function declares and resolving those dependencies. Below table lists the methods supported by the $injector service.

Name Descriptions
annotate(fn)Gets the arguments for the specified function, including those that do not correspond to services
get(name)Gets the service object for the specified service name
has(name)Returns true if a service exists for the specified name
invoke(fn, self, locals)Invoked the specified function, using the specified value for this and the specified non-service argument values.

The $injector service is right at the core of the AngularJS library, and there is rarely a need to work directly with it, but it can be useful for understanding and customizing how AngularJS works. However, these are the kind of customizations that should be considered carefully and tested thoroughly.

Determining Function Dependencies

JavaScript is a fluid and dynamic language, and there is a lot to recommend it, but it lacks the ability to annotate functions to manage their execution and behavior. Other languages, such as C#, support features such as attributes that are used to express instructions or metadata about a function. The lack of annotations means that AngularJS has to go to some extraordinary lengths to implement dependency injection, which is handled by matching the names of function arguments to services. Usually the person writing a function gets to decide the names of arguments, but in AngularJS the names take on a special significance. The annotate method defined by the $injector service is used to get the set of dependencies that a function has declared, as shown in below example.

I have changed the code of index.html and index.js file for demonstrate the above concept.
 
Index.html
  1. <!DOCTYPE html>  
  2. <html xmlns="http://www.w3.org/1999/xhtml" ng-app="TestApp">  
  3. <head>  
  4.     <title>Angular Injection</title>  
  5.     <script src="angular.js"></script>  
  6.     <script src="app.js"></script>  
  7.     <script src="Index.js"></script>  
  8.       
  9. </head>  
  10. <body ng-controller="indexController">  
  11.     <div class="well">  
  12.         <button class="btn btn-primary" ng-click="handleClick()">Click Me!</button>  
  13.     </div>  
  14. </body>  
  15. </html>  
Index.js
  1. testApp.controller("indexController"function ($scope, $log, $injector) {  
  2.     var counter = 0;  
  3.     var logClick = function ($log, $exceptionHandler, message) {  
  4.         if (counter == 0) {  
  5.             $log.log(message);  
  6.             counter++;  
  7.         } else {  
  8.             $exceptionHandler("Already clicked");  
  9.         }  
  10.     }  
  11.     $scope.handleClick = function () {  
  12.         var deps = $injector.annotate(logClick);  
  13.         for (var i = 0; i < deps.length; i++) {  
  14.             console.log("Dependency: " + deps[i]);  
  15.         }  
  16.     };  
  17. });  
The output of the code as below,

 
 

Getting the $injector Service from the Root Element

The $rootElement service provides access to the HTML element to which the ng-app directive is applied and which is the root of the AngularJS application. The $rootElement service is presented as a jqLite object, which means you can use jqLite to locate elements or modify the DOM using the jqLite methods I described in Chapter 15. Of interest in this chapter, the $rootElement service object has an additional method called injector, which returns the $injector service object. You can see how I replaced the dependency on the $injector service with the $rootElement service in the below example.

Index.html
  1. <!DOCTYPE html>  
  2. <html xmlns="http://www.w3.org/1999/xhtml" ng-app="TestApp">  
  3. <head>  
  4.     <title>Angular Injection</title>  
  5.     <script src="angular.js"></script>  
  6.     <script src="app.js"></script>  
  7.     <script src="Index.js"></script>  
  8.       
  9. </head>  
  10. <body ng-controller="indexController">  
  11.     <div class="well">  
  12.         <button class="btn btn-primary" ng-click="handleClick()">Click Me!</button>  
  13.     </div>  
  14. </body>  
  15. </html>  
 
Index.js
  1. testApp.controller("indexController"function ($scope, $log, $rootElement) {  
  2.     var counter = 0;  
  3.     var logClick = function ($log, $exceptionHandler, message) {  
  4.         if (counter == 0) {  
  5.             $log.log(message);  
  6.             counter++;  
  7.         } else {  
  8.             $exceptionHandler("Already clicked");  
  9.         }  
  10.     }  
  11.     $scope.handleClick = function () {  
  12.         var localVars = { message: "Button Clicked" };  
  13.         $rootElement.injector().invoke(logClick, null, localVars);  
  14.     };  
  15. });  
The output of the code is as below,

  

Similar Articles