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
- var testApp = angular.module('TestApp', []);
-
- testApp.config(function ($provide) {
- $provide.decorator("$log", function ($delegate) {
- $delegate.originalLog = $delegate.log;
- $delegate.log = function (message) {
- $delegate.originalLog("Decorated: " + message);
- }
- return $delegate;
- });
- });
Index.html
- <!DOCTYPE html>
- <html xmlns="http://www.w3.org/1999/xhtml" ng-app="TestApp">
- <head>
- <title>Angular Injection</title>
- <script src="angular.js"></script>
- <script src="app.js"></script>
- <script src="Index.js"></script>
-
- </head>
- <body ng-controller="indexController">
- <div class="well">
- <button class="btn btn-primary" ng-click="handleClick()">Click Me!</button>
- </div>
- </body>
- </html>
Index.js
- testApp.controller("indexController", function ($scope, $log) {
- $scope.handleClick = function () {
- $log.log("Button Clicked");
- };
- });
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
- <!DOCTYPE html>
- <html xmlns="http://www.w3.org/1999/xhtml" ng-app="TestApp">
- <head>
- <title>Angular Injection</title>
- <script src="angular.js"></script>
- <script src="app.js"></script>
- <script src="Index.js"></script>
-
- </head>
- <body ng-controller="indexController">
- <div class="well">
- <button class="btn btn-primary" ng-click="handleClick()">Click Me!</button>
- </div>
- </body>
- </html>
Index.js
- testApp.controller("indexController", function ($scope, $log, $injector) {
- var counter = 0;
- var logClick = function ($log, $exceptionHandler, message) {
- if (counter == 0) {
- $log.log(message);
- counter++;
- } else {
- $exceptionHandler("Already clicked");
- }
- }
- $scope.handleClick = function () {
- var deps = $injector.annotate(logClick);
- for (var i = 0; i < deps.length; i++) {
- console.log("Dependency: " + deps[i]);
- }
- };
- });
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
- <!DOCTYPE html>
- <html xmlns="http://www.w3.org/1999/xhtml" ng-app="TestApp">
- <head>
- <title>Angular Injection</title>
- <script src="angular.js"></script>
- <script src="app.js"></script>
- <script src="Index.js"></script>
-
- </head>
- <body ng-controller="indexController">
- <div class="well">
- <button class="btn btn-primary" ng-click="handleClick()">Click Me!</button>
- </div>
- </body>
- </html>