AngularJS Framework : Dependency Injection
Dependency Injection (DI) is a software design pattern that deals with how components get hold of their dependencies.
The Angular injector subsystem is in charge of creating components, resolving their dependencies, and providing them to other components as requested
- from https://docs.angularjs.org/guide/di.
Here is a brief description from the same source: https://docs.angularjs.org/guide/di.
There are three ways a component (object or function) can get a hold of its dependencies:
- The component can create the dependency, typically using the new operator.
- The component can look up the dependency, by referring to a global variable.
- The component can have the dependency passed to it where it is needed.
The first two options of creating or looking up dependencies are not optimal because they hard code the dependency to the component. This makes it difficult, if not impossible, to modify the dependencies. This is especially problematic in tests, where it is often desirable to provide mock dependencies for test isolation.
The third option is the most viable, since it removes the responsibility of locating the dependency from the component. The dependency is simply handed to the component.
Our first encounter with dependency injection is the most likely the $scope parameter in a controller. Angular's Dependency Injection allows us to write code like the following without taking into account where $scope comes from.
<script> function NameController($scope) { $scope.names = ["Debussy", "Ravel"]; $scope.add = function() { $scope.names.push($scope.newNames); $scope.newNames = ""; }; $scope.remove = function(name) { var n = $scope.names.indexOf(name); $scope.names.splice(n,1); }; } </script>
In the code, $scope gets injected by Angular whenever this controller is instantiated. Soon, we'll see how the dependency injector resolves the correct values.
Angular takes this approach because it allows us to lessen the worries of dependencies and to focus on unit-testing. We can, for example, configure the DI framework to use mock-objects for underlying components instead of real services during our unit tests.
AngularJS contains the following core object types and components:
- Value
- Factory
- Service
- Provider
- Constant
These core types can be injected into each other using AngularJS dependency injection.
Module.value(key, value) is used to inject an editable value, while Module.constant(key, value) is used to inject a constant value. Here are specific examples that add values to an AngularJS module:
var myModule = angular.module("myModule", []); myModule.value("numberVal", 123); myModule.value("stringVal", "Ravel"); myModule.value("objectVal", { val1 : 999, val2 : "Debussy"} );
A value in AngularJS is a simple object, it is typically used as configuration which is injected into factories, services or controllers. Injecting a value into an AngularJS controller function is done simply by adding a parameter with the same name as the value. It is the first parameter passed to the value() function when the value is defined.
var myModule = angular.module("myModule", []); myModule.value("stringVal", "Debussy") myModule.controller("MyController", function($scope, stringVal) { console.log(stringVal); });
The following code (dependency_injection_A.html) is using ng-repreat to create td of the table after getting the data from JSON form.
<html ng-app="countryApp"> <head> <meta charset="utf-8"> <title>Angular.js Example</title> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script> <script> var countryApp = angular.module('countryApp', []); countryApp.controller('CountryController', function ($scope){ $scope.countries = [ {"name": "China", "population": 1366450000}, {"name": "India", "population": 1248610000}, {"name": "USA", "population": 318651000}, {"name": "Indonesia", "population": 252164800}, {"name": "Brasil", "population": 203073000}, {"name": "Pakistan", "population": 188020000}, {"name": "Nigeria", "population": 178517000} ]; }); </script> </head> <body ng-controller="CountryController"> <table> <tr> <th>Country</th> <th>Population</th> </tr> <tr ng-repeat="country in countries"> <td>{{country.name}}</td> <td align="right">{{country.population}}</td> </tr> </table> </body> </html>
After the rendering, the page should look like this:
In practice, we fetch the country's data from somewhere Internet (countries.json). The following code (dependency_injection_B.html) is an extended version of the code above, and we can see dependency injection is at work.
<html ng-app="countryApp"> <head> <meta charset="utf-8"> <title>Angular.js Example</title> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script> <script> var countryApp = angular.module('countryApp', []); countryApp.controller('CountryController', function ($scope, $http){ $http.get('http://www.bogotobogo.com/AngularJS/files/Dependency_Injection/countries.json').success(function(data) { $scope.countries = data; }); }); </script> </head> <body ng-controller="CountryController"> <table> <tr> <th>Country</th> <th>Population</th> </tr> <tr ng-repeat="country in countries"> <td>{{country.name}}</td> <td align="right">{{country.population}}</td> </tr> </table> </body> </html>
Then, we'll have the page that should look exactly like the previous output:
Note that the changes we made is small; from:
countryApp.controller('CountryController', function ($scope){ $scope.countries = [ {"name": "China", "population": 1366450000}, {"name": "India", "population": 1248610000}, {"name": "USA", "population": 318651000}, {"name": "Indonesia", "population": 252164800}, {"name": "Brasil", "population": 203073000}, {"name": "Pakistan", "population": 188020000}, {"name": "Nigeria", "population": 178517000} ]; });
to:
countryApp.controller('CountryController', function ($scope, $http){ $http.get('http://www.bogotobogo.com/AngularJS/files/Dependency_Injection/countries.json').success(function(data) { $scope.countries = data; }); });
Basically, we've just modified the function signature, and besides the $scope, we now have additional $http in our controller:
countryApp.controller('CountryController', function ($scope, $http){
This $http injection tells Angular to give us the http module. So, Angular injects resources, and thanks to this Angular's dependency injection mechanism, we can tap it by just adding $http to the function signature. We get data via callback function for success() which is a promise (A promise represents the eventual result of an operation. We can use a promise to specify what to do when an operation eventually succeeds or fails).
$http.get('http://www.bogotobogo.com/AngularJS/files/Dependency_Injection/countries.json').success(function(data) {
Actually, the $http is a wrapper for the XMLHttpRequest. The $http service is a core Angular service that facilitates communication with the remote HTTP servers via the browser's XMLHttpRequest object or via JSONP.
(for more info, please check https://docs.angularjs.org/api/ng/service/$http)
When we minify JavaScript, the JavaScript minifier replaces the names of local variables and parameters with shorter names. However, AngularJS uses the parameter names of controller functions, factories, services and providers to decide what to inject into their factory functions. If the names are changed, AngularJS cannot inject the correct objects.
So, to make our AngularJS code minification safe, we need to provide the names of the objects to inject as strings. We need to wrap these strings in an array together with the function that needs the values injected. Here is an AngularJS minification safe dependency injection example (dependency_injection_minification.html) which is a slightly modified version from the one in the previous section.
<html ng-app="countryApp"> <head> <meta charset="utf-8"> <title>Angular.js Example</title> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script> <script> var countryApp = angular.module('countryApp', []); countryApp.controller('CountryController', ['$scope', '$http', function (scope, http){ http.get('http://www.bogotobogo.com/AngularJS/files/Dependency_Injection/countries.json').success(function(data) { scope.countries = data; }); }]); </script> </head> <body ng-controller="CountryController"> <table> <tr> <th>Country</th> <th>Population</th> </tr> <tr ng-repeat="country in countries"> <td>{{country.name}}</td> <td align="right">{{country.population}}</td> </tr> </table> </body> </html>
As we see, the only change we made is this part of the code:
countryApp.controller('CountryController', ['$scope', '$http', function (scope, http){ http.get('http://www.bogotobogo.com/AngularJS/files/Dependency_Injection/countries.json').success(function(data) { scope.countries = data; }); }]);
AngularJS
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization