In the last part, we went had a thorough look at scopes, and scope “inheritance”. That means that we have now covered setting up Angular, creating modules and controllers, and how to utilize two-way data binding using the scope object. The next step is to learn how to work with dependency injection in Angular to provide the same dependency injected service structure that Angular uses internally.
Dependency injection isn’t complicated as such, you register a service that another unit of code can request to get access to. That is the extremely simple definition. To support this in Angular we depend on the $provide service. This service is used to locate dependencies that other pieces of code require. Unfortunately, something as simple as this, actually becomes a little complicated and hard to grasp in Angular. Not because it is really complicated, but because there are so many options…
Angular offers 3 different ways to register services to inject. They all do the same thing more or less, but each works a little bit differently. On top of that, Angular gives us 2 ways to register “values” to get them injected just like services. And on top of that, Angular gives us 2 ways to access these 5 different registration functions… As you can see, with that many options, it can easily become a little overwhelming… But let’s go through the options slowly, and see if we can’t make it all understandable…
First of all, let’s look at the 2 ways to get to the “registration functions”. The first one, and most used one I think, is by using the module API. All modules will give you access to all the 5 different registration functions, which makes fluid registration possible just like when adding controllers. The available functions are constant(), value(), service(), factory() and provider().
So to register a value to be available through dependency injection, your would write something like this
angular.module('myModule',[])
.value('myValue', 'Hello World');
This will give us a string value that can be injected into controllers etc by just requesting them in the “constructor” like this
angular.module('myModule',[])
...
.controller('myController', ['$scope', 'myValue', function($scope, myValue) {
$scope.value = myValue;
}]);
Running this in my browser with a HTML page like this
<!DOCTYPE html>
<html data-ng-app="myModule">
<head>
<title>A simple introduction to AngularJS - Part 6</title>
<script src="Scripts/angular.min.js"></script>
<script src="Scripts/app.js"></script>
</head>
<body data-ng-controller="myController">
{{value}}
</body>
</html>
will produce something like this
Ok, so that is by far the simplest “service” you can think of, using the simplest possible way to register it…
It’s worth nothing that a registered “value” can be any type you want. It can be simple values, as well as complex objects and functions and so on. But generally, “values” should be simple values. If you want functions and objects and so on, it is often better to consider it a service, and register it as such…
The other way to register something for injection is to use the $provide service, which is actually being used under the hood when registering the value above.
So, how do we get hold of the $provide service? Well, the most common way, I believe, would be while configuring your module. And what do I mean by “configuring your module”? Well, when I walked through creating modules in the post about modules, I skipped over a bunch of the more advanced functionality. Among the skipped functionality, is the ability to register a configuration function that will be called when the module is initialized. You can do this in two ways. You can either pass in a third parameter when creating the module, or by using the modules config() function.
angular.module('myModule',[], function() {
console.log('Config!!!');
});
// OR
angular.module('myModule',[])
.config(function() {
console.log('Config!!!');
})
);
In the config function, you can request certain objects to be injected. You won’t have access to all the applications services in this case, so you can’t get them injected, but you can get some other stuff injected (I will get back a bit to this later…). Among the things you can have injected is the $provide service. Using this service, you can register your own services like this
angular.module('myModule',[], ['$provide', function($provide) {
$provide.value('myValue', 'Hello World');
}])
...
);
The result is exactly the same, but the configuration function gives you a bit more flexibility in how you create the objects that you register for injection.
However, as both the ways of registering services for injection do the same thing, I will stick with the previous way of doing it from now on as that seems to be the most common way.
Ok, so we have seen how we register objects for dependency injection. Or rather, we have seen how to register a value for dependency injection. However, as mentioned before, that is only one of ways to register things for injection…
Another way is by using the constant() function. The constant() function will register a value in much the same way as value() will. However, there are some differences. When registering a value using value() the value will not be available for injection in module configuration functions. But if you register the value using constant() it will. This could for example be a way to switch out environment variables and such, and use them when configuring the modules using the configuration functions…
Registration-wise, value() and constant() are identical
angular.module('myModule',[])
.config(['myConst', function(myConst) {
console.log(myConst);
}])
.constant('myConst', 'My Constant Value');
In the above, the console will output “My Constant Value” as expected. And yes, I am using console.log, which is not recommended at all. But at config time, services like $log aren’t available as mentioned before.
Ok, so value() and constant() are fairly simple to understand. Except for the differences between them, which I guess are somewhat nitpicky. But the actual registration isn’t that complicated. Take a value, give it a name, and register it for injection.
Injecting functionality, or “services”, is a little bit more complicated as there are 3 ways to provide the value to inject…
One of the more common ways to register injectable functionality is to use the factory() function. The factory() function takes a name, just as previous registrations, and a factory function. A factory function is a function that is used to create the object to be used for injection.
Note! All “services”, or “injected objects”, are singletons. There will only be one instance that is injected into all places where they are requested. Even if creating a factory function indicates that you might actually create several instances, it is actually only called once to generate a singleton to use for injection. If you can’t use singletons, you can get around it by creating a singleton service that acts as a factory and creates new instances on demand. However, Angular will only register singletons…
So, to register a bit of functionality for injection using the factory() function, you would write something like this
angular.module('myModule',[])
.factory('myService', ['$log', function($log) {
return {
logSomething: function(val) { $log.info(val); }
};
}])
.controller('myController', ['myService', function(myService) {
myService.logSomething('Hello World');
}]);
As you can see from the code, a service registered using factory() is fully injectable. In this case, the service requires the $log service, which is injected when the factory function is called. The factory function returns an object, which is the object injected whenever the “myService” service is requested.
You can return as complicated, or simple objects as you want- The sky is the limit. The only rule is that you have to return an object which is to be injected whenever a service with the supplied name is requested…
The factory() function has a sibling function called service(). On the surface, they are very similar, but they are actually quite different. While the factory() will require you to return an object to be used for injection, the service() takes a constructor function that will be invoked using new and then returned.
Ok, what the heck does that mean? Well, if you aren’t a JavaScript person, that probably made little sense to you…if you are, and it did, just skip this paragraph… The factory() function will register a function that, when requested for injection, will be called, return a value, and the value is injected. The service() function takes a function that, when requested for injection is called using the new keyword, and then returned. So instead of being called to create an object to be injected, it is instantiated, and the instance injected.
So, after that sidestep, registering a service using the service() function looks like this
angular.module('myModule',[])
.service('myService', ['$log', function($log) {
this.logSomething = function(val) { $log.info(val);
};
}])
.controller('myController', ['myService', function(myService) {
myService.logSomething('Hello World');
}]);
Notice how I use this to add the logSomething() function, instead of creating a new object, and returning that.
Ok, so that wasn’t too complicated, but is still the cause of a lot of confusion it seems. And to be honest, before writing this post, and looking into it further, my own definition of service() vs factory() was a bit vague…
The last flavor, and by far most complicated way, of registering services, is the provider() function. The provider() function is actually in play using the other flavors as well, but it is hidden behind a simplified API.
The provider() function takes 2 parameters just as the other functions. A name, and a function. Just like the other… However, the way the function is used is a bit different… The function is a constructor function, so it will be called using the new keyword. It is expected to populate a property called $get with a factory function. And by factory function, I mean a function that will be called just like the function used when using the factory() function. However, just as in that case, you can return an array including the dependencies the function requires…
Ok, that potentially wasn’t too easy to understand, so let’s look at the code
angular.module('myModule',[])
.provider('myService', function() {
this.$get = ['$log', function($log) {
return {
logSomething: function(val) { $log.info(val); }
}
}];
})
.controller('myController', ['myService', function(myService) {
myService.logSomething('Hello World Provider');
}]);
As you can see, the code registers a provider for a service called myService. The provider constructor function sets the $get property to an array, which is identical to the array you would use when using the factory() function.
There are a few important things to understand here. First of all, I said that you registered a provider, not a service. This actually registers a provider named myServiceProvider. This provider’s $get will be used by the injector as a factory function when the service myService is requested.
Ok, so why would I go through something this complex to register a service? Well, it gives you some functionality that the other ways don’t… I mentioned previously that not all services were available for injection into module configuration functions. However, all service providers registered before the config function runs are available for injection. That way, we can configure the providers during configuration, and in this way configure the object returned by the factory function.
That might sound useless, and quite complicated, but let’s look at some code. Imagine that we want to configure the myService to include a prefix to everything that is logged. There are two ways of doing this. We could register the prefix as a “value service” and get it injected, or we could use a provider, and configuration to do it. Like this
angular.module('myModule',[])
.provider('myService', function() {
var _prefix = '';
this.setPrefix = function(prefix) {
_prefix = prefix;
};
this.$get = ['$log', function($log) {
return {
logSomething: function(val) { $log.info(_prefix + val); }
}
}];
})
.config(['myServiceProvider', function(myServiceProvider) {
myServiceProvider.setPrefix('>>> ');
}])
.controller('myController', ['myService', function(myService) {
myService.logSomething('Hello World Provider');
}]);
Ok, that hopefully made it a bit clearer… As you can see, the provider now contains a function called setPrefix(). This is used in the modules configuration function to set the prefix to use.
Just note that the modules configuration function requests a value named myServiceProvider, no myService. These are 2 different things. The provider provides a factory function that “provides” the object to use when performing injection. And the provider is accessible when doing module configuration, the service is not, it hasn’t been created yet…
Ok, that is pretty much all there is to defining services…or defining injected values…in Angular! However, there is one last feature that can be interesting to note, and that is the ability to create “decorators”.
Decorators are methods that intercept the service creation, and are given the ability to modify the service, or even replace it, before it is injected. This can be very useful for a whole heap of things.
They are defined using the decorator() function of the $provide service. It takes 2 parameters, the name of the service to decorate, and a function. And by function, I mean either a pure function, or an array for an dependency injected function. The function can get any other service injected, including a special one called $delegate, which is a reference to the original service.
Imagine that we wanted to modify the $log service so that every log entry was preceded by the applications name… Yes, a very contrived example, but still an example…
To do this, we could either modify the $log at the source, which would be a PITA, create a custom service for the logging, make sure we remember to log the application name every time we use the log, or we could add a decorator to modify the $log service. In this case, modifying the source is probably not a good idea, as it would mean branching Angular, so using a decorator is probably the best way way.
To do this modification using a decorator, we would do something like this
angular.module('myModule',[])
.config(['$provide', function($provide) {
$provide.decorator('$log', ['$delegate', function($delegate) {
var fn = $delegate.info;
$delegate.info = function(val) {
fn('Application');
fn(val);
};
return $delegate;
}])
}])
.controller('myController', ['$log', function($log) {
$log.info('Hello From Decorated Log');
}]);
As you can see, the code is monkey patching the $log service by storing a reference to the original info() function, and then replacing it with another implementation.
Yes, a very contrived example, but it still shows the idea of using decorators. It is a great way to be able to get in and change/extend registered services. Just remember that the service you are decorating has to registered before trying to decorate it. This might sound obvious, but if you use the fluent syntax for registering modules and services, calls to register services must be made before the call to config() where you can create the decorator by using the $provide…
That is pretty much all you need to know about creating dependency injected objects, or services if you so wish, which means that I will round it off here, and leave you to play around with your new found knowledge.
Cheers for now!