- AngularJS Tutorial
- AngularJS Views And Directives
- AngularJS $scope Hierarchy
- AngularJS Events
- AngularJS $watch() , $digest() and $apply()
- AngularJS AJAX
- AngularJS Forms
- AngularJS $timeout and $interval
- AngularJS Custom Directives
- AngularJS Modularization & Dependency Injection
- AngularJS Routes
- AngularJS Internationalization
- AngularJS Critique
AngularJS Modularization & Dependency Injection
Jakob Jenkov |
AngularJS comes with a built-in dependency injection mechanism. You can divide your application into multiple different types of components which AngularJS can inject into each other. Modularizing your application makes it easier to reuse, configure and test the components in your application.
AngularJS contains the following core types of objects and components:
- Value
- Factory
- Service
- Provider
- Constant
These core types can be injected into each other using AngularJS dependency injection mechanism. Throughout the rest of this text I will explain how to define and inject these components into each other.
Value
A value in AngularJS is a simple object. It can be a number, string or JavaScript object. Values are typically used as configuration which is injected into factories, services or controllers.
A value has to belong to an AngularJS module. Here are three examples that add values to an AngularJS module:
var myModule = angular.module("myModule", []); myModule.value("numberValue", 999); myModule.value("stringValue", "abc"); myModule.value("objectValue", { val1 : 123, val2 : "abc"} );
The values are defined using the value()
function on the module. The first
parameter is the name of the value, and the second parameter is the value itself. Factories,
services and controllers can now reference these values by their name.
Injecting a Value
Injecting a value into an AngularJS controller function is done simply by adding a parameter
with the same name as the value (the first parameter passed to the value()
function
when the value is defined). Here is an example:
var myModule = angular.module("myModule", []); myModule.value("numberValue", 999); myModule.controller("MyController", function($scope, numberValue) { console.log(numberValue); });
Notice how the second parameter of the controller function has the same name as the value.
Factory
Factory is a function that creates values. When a service, controller etc. needs a value injected from a factory, the factory creates the value on demand. Once created, the value is reused for all services, controllers etc. which need it injected. Thus, a factory differs from a value in that it can use a factory function to create the object it returns. You can also inject values into a factory for use when creating the object. You cannot do that with a value.
Here is an example that defines a factory on a module, and a controller which gets the factory created value injected:
var myModule = angular.module("myModule", []); myModule.factory("myFactory", function() { return "a value"; }); myModule.controller("MyController", function($scope, myFactory) { console.log(myFactory); });
As you can see, it is very similar to defining and injecting a value object. Keep in mind that it is not the factory function that is injected, but the value produced by the factory function.
Injecting Values Into a Factory
You can inject a value into a factory. It works just like when a value is injected into a controller. Here is an example:
var myModule = angular.module("myModule", []); myModule.value("numberValue", 999); myModule.factory("myFactory", function(numberValue) { return "a value: " + numberValue; });
In this example the injected value is used to create the object created by the factory function.
Service
A service in AngularJS is a singleton JavaScript object which contains a set of functions. The functions contain whatever logic is necessary for the service to carry out its work.
AngularJS services are created using the service()
function on a module. Here is an example:
function MyService() { this.doIt = function() { console.log("done"); } } var myModule = angular.module("myModule", []); myModule.service("myService", MyService);
As you can see, services are defined somewhat differently than factories and values. First of all,
the service is defined as a separate, named function. That is because services in AngularJS are
created using the new
keyword. Thus, AngularJS will do this internally:
var theService = new MyService();
Apart from defining services as functions with functions inside, you add them to and use them with AngularJS just like you would with a value or function. You inject a service into a controller like this:
function MyService() { this.doIt = function() { console.log("done"); } } var myModule = angular.module("myModule", []); myModule.service("myService", MyService); myModule.controller("MyController", function($scope, myService) { myService.doIt(); });
Injecting Values Into a Service
You can inject values into a service, just like you can inject values into controllers, or services into controllers etc. Here is an example:
var myModule = angular.module("myModule", []); myModule.value ("myValue" , "12345"); function MyService(myValue) { this.doIt = function() { console.log("done: " + myValue; } } myModule.service("myService", MyService);
Notice how the parameter to the MyService
function is named the same as
the value registered on the module. Thus, the value will be injected into the service
when it is created.
Providers
Providers in AngularJS is the most flexible form of factory you can create. You register a
provider with a module just like you do with a service or factory, except you use the
provider()
function instead. Here is an AngularJS provider example:
var myModule = angular.module("myModule", []); myModule.provider("mySecondService", function() { var provider = {}; provider.$get = function() { var service = {}; service.doService = function() { console.log("mySecondService: Service Done!"); } return service; } return provider; });
As you can see, the provider()
function takes 2 parameters. The first parameter is the
name of the service / object which the provider creates. In this case the name is mySecondService
.
The second parameter is the function which creates the provider. Note: The provider is itself a factory, so
at this time no actual service or object is created from the provider. Only the function creating the provider
is defined.
When you look at the function creating the provider you can see that the provider is a JavaScript object.
The JavaScript provider object contains a single $get()
function. This is the factory function
of the provider. In other words, the $get()
function creates whatever the provider creates
(service, value etc.). In the example above, the provider creates a service object which contains a single
service function (standard JavaScript function) called doService()
.
In order to get the product of a provider injected into a controller, do specify a dependency on the provider, just like you would with a service. What is injected into the controller is the product created by the provider, not the provider itself. Here is an AngularJS provider injection example:
myModule.controller("MyController", function($scope, mySecondService) { $scope.whenButtonClicked = function() { mySecondService.doIt(); } });
As you can see, the name of the provider is used as a parameter in the controller function.
The object created by the provider's $get()
function will be injected into this parameter.
Configuring a Provider
It is possible to configure a provider further via calls to its function during the configuration phase of a module. Here is an example:
var myModule = angular.module("myModule", []); myModule.provider("mySecondService", function() { var provider = {}; var config = { configParam : "default" }; provider.doConfig = function(configParam) { config.configParam = configParam; } provider.$get = function() { var service = {}; service.doService = function() { console.log("mySecondService: " + config.configParam); } return service; } return provider; }); myModule.config( function( mySecondServiceProvider ) { mySecondServiceProvider.doConfig("new config param"); }); myModule.controller("MyController", function($scope, mySecondService) { $scope.whenButtonClicked = function() { mySecondService.doIt(); } });
Notice how the provider object now has an extra function called doConfig()
. This function
can be used to set a configuration parameter on the provider.
Notice also the call to the myModule.config()
function. The config
function
takes a function as parameter. This function can configure the module. The function passed to
config()
takes a single parameter named mySecondServiceProvider
. That
is the same name the provider is registered with plus Provider
as suffix. The suffix tells
AngularJS to inject the provider itself, and not the object created by the provider. Inside the
function passed to the config()
function the mySecondServiceProvider.doConfig()
function
is called, which sets the config parameter on the provider.
The controller defined later in the example just depends on the object created by the provider
(not the provider itself).
It does so by taking a parameter named mySecondService
which is the name the provider
of the service is registered with. As you can see, the service used from inside the
$scope.whenButtonClicked()
function.
Constants
In the previous section on providers you saw how to configure a provider via the module.config()
function. Unfortunately you cannot inject values into the module.config()
function. Instead you
can inject constants.
Constants in AngularJS are defined using the module.constants()
function. Here is an
AngularJS constant example:
myModule.constant("configValue", "constant config value");
This constant can now be injected into the module.config()
function like this:
myservices.config( function( mySecondServiceProvider, configValue ) { mySecondServiceProvider.doConfig(configValue); });
As you can see, the parameter configValue
matches the name of the constant which is also
configValue
. Thus the value of the constant will be injected into this parameter.
The constant value is then passed as parameter to the doConfig()
function on the
mySecondServiceProvider
provider.
Dependencies Between Modules
As you have seen, values, factories and services are added to an AngularJS module. It is possible for one module to use the values, factories and services of another module. In order to do so, a module needs to declare a dependency on the module which contains the values, factories and services it wants to use. Here is an example:
var myUtilModule = angular.module("myUtilModule", []); myUtilModule.value ("myValue" , "12345"); var myOtherModule = angular.module("myOtherModule", ['myUtilModule']); myOtherModule.controller("MyController", function($scope, myValue) { });
Notice how the second module (myOtherModule
) lists the name of the
first module (myUtilModule
) in the second parameter (inside the array) passed
to the angular.module()
function. This tells AngularJS that all values, factories
and services defined inside the myUtilModule
should be available inside
the myOtherModule
module too. In other words, myOtherModule
depends on
myUtilModule
.
Second, notice how the MyController
controller function now declares
a parameter named myValue
. This value will be provided from the value
registered on the myUtilModule
module.
Minification Safe Dependency Injection in AngularJS
When you 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.
To make your AngularJS code minification safe, you need to provide the names of the objects to inject as strings. You wrap these strings in an array together with the function that needs the values injected. Here is an AngularJS minification safe dependency injection example:
var myapp = angular.module("myapp", ['myservices']); myapp.controller("AController", ['$scope', function(p1) { p1.myvar = "the value"; }]);
This example injects the $scope
object into the p1
parameter of
the controller function.
Notice how the controller function is registered. Instead of passing the controller function
to the angular.controller
function directly, an array is passed instead. This
array contains the name of the value to inject into the controller function, as well as the
controller function itself. The controller function is always the last value in this array.
If you need to inject more than one value, the value names are listed in the beginning
of the array and in the sequence they are to be injected into the function. Here is a
minification safe multi value example:
var myapp = angular.module("myapp", ['myservices']); myapp.controller("AController", ['$scope', '$http', function(p1, p2) { p1.myvar = "the value"; p2.get("/myservice.json"); }]);
This example injects the $scope
object into the p1
parameter,
and the $http
service into the p2
parameter of the controller function.
Now it no longer matters what the parameter names of the controller function are. AngularJS will use the strings in the beginning of the array to determine what to inject into the controller function.
The same mechanism can be used for factories, services and providers to provide minification safe dependency injection. Here is a minification safe factory, service and provider example:
var myutil = angular.module("myutil", []); myutil.value("safeValue", "a safe value"); myutil.factory("safeFactory", ['safeValue', function(p1) { return { value : p1 }; }]); function MySafeService(p1){ this.doIt = function() { return "MySafeService.doIt() called: " + p1.value; } } myutil.service("safeService", ['safeFactory', MySafeService]); myutil.provider("safeService2", function() { var provider = {}; provider.$get = ['safeService', function(p1) { var service = {}; service.doService = function() { console.log("safeService from provider: " + p1.doIt()); } return service; }]; return provider; }); myapp.controller("AController", ['$scope', 'safeService2', function(p1, p2) { p1.myvar = "the value"; p2.doService(); }]);
Notice especially the declaration of the provider. Notice how the dependencies are not
specified on the provider factory function, but on the $get()
function
of the provider returned from inside the provider factory function. Actually, an array
with names of dependencies and the function implementation is used instead of just a $get()
function. Other than that, specifying dependencies works the same for providers as for factories,
services and controller functions.
Tweet | |
Jakob Jenkov |