- Notifications
You must be signed in to change notification settings - Fork27.4k
Understanding Dependency Injection
Dependency injection in AngularJS is supremely useful, and the key to making easily testable components. This article explains how Angular's dependency injection system works.
The Provider ($provide
)
The$provide
service is responsible for telling Angular how to create new injectable things; these things are calledservices. Services are defined by things calledproviders, which is what you're creating when you use$provide
. Defining a provider is done via theprovider
method on the$provide
service, and you can get hold of the$provide
service by asking for it to be injected into an application'sconfig
function. An example might be something like this:
myMod.config(function($provide){$provide.provider('greeting',function(){this.$get=function(){returnfunction(name){alert("Hello, "+name);};};});});
Here we've defined a new provider for a service calledgreeting
; we can inject a variable namedgreeting
into any injectable function (like controllers, more on that later) and Angular will call the provider's$get
function in order to return a new instance of the service. In this case, the thing that will be injected is a function that takes aname
parameter andalert
s a message based on the name. We might use it like this:
myMod.controller('MainController',function($scope,greeting){$scope.onClick=function(){greeting('Ford Prefect');};});
Now here's the trick.factory
,service
, andvalue
are all just shortcuts to define various parts of a provider--that is, they provide a means of defining a provider without having to type all that stuff out. For example, you could write thatexact same provider just like this:
myMod.config(function($provide){$provide.factory('greeting',function(){returnfunction(name){alert("Hello, "+name);};});});
It's important to understand, so I'll rephrase: under the hood, AngularJS is calling theexact same code that we wrote above (the$provide.provider
version)for us. There is literally, 100% no difference in the two versions.value
works just the same way--if whatever we would return from our$get
function (aka ourfactory
function) is always exactly the same, we can write even less code usingvalue
. For example, since we always return the same function for ourgreeting
service, we can usevalue
to define it, too:
myMod.config(function($provide){$provide.value('greeting',function(name){alert("Hello, "+name);});});
Again, this is 100% identical to the other two methods we've used to define this function--it's just a way to save some typing.
Now you probably noticed this annoyingmyMod.config(function($provide) { ... })
thing I've been using. Since defining new providers (viaany of the given methods above) is so common, AngularJS exposes the$provide
methods directly on the module object, to save even more typing:
varmyMod=angular.module('myModule',[]);myMod.provider("greeting", ...);myMod.factory("greeting", ...);myMod.service("greeting", ...);myMod.value("greeting", ...);
These all do the same thing as the more verboseapp.config(...)
versions we used previously.
The one injectable I've skipped so far isconstant
. For now, it's easy enough to say that it works just likevalue
. We'll see there's one difference later.
To review,all these pieces of code are doing theexact same thing:
myMod.provider('greeting',function(){this.$get=function(){returnfunction(name){alert("Hello, "+name);};};});myMod.factory('greeting',function(){returnfunction(name){alert("Hello, "+name);};});myMod.service('greeting',function(){returnfunction(name){alert("Hello, "+name);};});myMod.value('greeting',function(name){alert("Hello, "+name);});
The Injector ($injector
)
The injector is responsible for actually creating instances of our services using the code we provided via$provide
(no pun intended). Any time you write a function that takes injected arguments, you're seeing the injector at work. Each AngularJS application has a single$injector
that gets created when the application first starts; you can get a hold of it by injecting$injector
into any injectable function (yes,$injector
knows how to inject itself!)
Once you have$injector
, you can get an instance of a defined service by callingget
on it with the name of the service. For example,
vargreeting=$injector.get('greeting');greeting('Ford Prefect');
The injector is also responsible for injecting services into functions; for example, you can magically inject services into any function you have using the injector'sinvoke
method;
varmyFunction=function(greeting){greeting('Ford Prefect');};$injector.invoke(myFunction);
It's worth noting that the injector will only create an instance of a serviceonce. It then caches whatever the provider returns by the service's name; the next time you ask for the service, you'll actually get the exact same object.
So, it stands to reason that you can inject services intoany function that is called with$injector.invoke
. This includes
- controller definition functions
- directive definition functions
- filter definition functions
- the
$get
methods of providers (aka thefactory
definition functions)
Sinceconstant
s andvalue
s always return a static value, they are not invoked via the injector, and thus you cannot inject them with anything.
You may be wondering why anyone would bother to set up a full-fledged provider with theprovide
method iffactory
,value
, etc. are so much easier. The answer is that providers allow a lot of configuration. We've already mentioned that when you create a service via the provider (or any of the shortcuts Angular gives you), you create a new provider that defines how that service is constructed. What Ididn't mention is that these providers can be injected intoconfig
sections of your application so you can interact with them!
First, Angular runs your application in two phases--theconfig
andrun
phases. Theconfig
phase, as we've seen, is where you can set up any providers as necessary. This is also where directives, controllers, filters, and the like get set up. Therun
phase, as you might guess, is where Angular actually compiles your DOM and starts up your app.
You can add additional code to be run in these phases with themyMod.config
andmyMod.run
functions--each take a function to run during that specific phase. As we saw in the first section, these functions are injectable--we injected the built-in$provide
service in our very first code sample. However, what's worth noting is thatduring theconfig
phase, only providers can be injected (with the exception of the services in theAUTO
module--$provide
and$injector
).
For example, the following isnot allowed:
myMod.config(function(greeting){// WON'T WORK -- greeting is an *instance* of a service.// Only providers for services can be injected in config blocks.});
What youdo have access to are anyproviders for services you've made:
myMod.config(function(greetingProvider){// a-ok!});
There is one important exception:constant
s, since they cannot be changed, are allowed to be injected insideconfig
blocks (this is how they differ fromvalue
s). They are accessed by their name alone (noProvider
suffix necessary).
Whenever you defined a provider for a service, that provider gets namedserviceProvider
, whereservice
is the name of the service. Now we can use the power of providers to do some more complicated stuff!
myMod.provider('greeting',function(){vartext='Hello, ';this.setText=function(value){text=value;};this.$get=function(){returnfunction(name){alert(text+name);};};});myMod.config(function(greetingProvider){greetingProvider.setText("Howdy there, ");});myMod.run(function(greeting){greeting('Ford Prefect');});
Now we have a function on our provider calledsetText
that we can use to customize ouralert
; we can get access to this provider in aconfig
block to call this method and customize the service. When we finally run our app, we can grab thegreeting
service, and try it out to see that our customization took effect.
Since this is a more complex example, here's a working demonstration:http://jsfiddle.net/BinaryMuse/9GjYg/
Controllers ($controller
)
You can inject things into controllers, but you can't inject controllers into things. That's because controllers aren't created via the provider. Instead, there is a built-in Angular service called$controller
that is responsible for setting up your controllers. When you callmyMod.controller(...)
, you're actually accessingthis service's provider, just like in the last section.
For example, when you define a controller like this:
myMod.controller('MainController',function($scope){// ...});
What you're actually doing is this:
myMod.config(function($controllerProvider){$controllerProvider.register('MainController',function($scope){// ...});});
Later, when Angular needs to create an instance of your controller, it uses the$controller
service (which in turn uses the$injector
to invoke your controller function so it gets its dependencies injected too).
filter
anddirective
work exactly the same way ascontroller
;filter
uses a service called$filter
and its provider$filterProvider
, whiledirective
uses a service called$compile
and its provider$compileProvider
. Some links:
- $filter:http://docs.angularjs.org/api/ng.$filter
- $filterProvider:http://docs.angularjs.org/api/ng.$filterProvider
- $compile:http://docs.angularjs.org/api/ng.$compile
- $compileProvider:http://docs.angularjs.org/api/ng.$compileProvider
As per the other examples,myMod.filter
andmyMod.directive
are shortcuts to configuring these services.
So, to summarize, any function that gets called with$injector.invoke
can be injected into. This includes, but is not limited to:
- controller
- directive
- factory
- filter
- provider
$get
(when defining provider as an object) - provider function (when defining provider as a constructor function)
- service
The provider creates new servicesthat can be injected into things. This includes:
- constant
- factory
- provider
- service
- value
That said, built-in services like$controller
and$filter
can be injected, and you canuse those services to get hold of the new filters and controllers you defined with those methods (even though the things you defined aren't, by themselves, able to be injected into things).
Other than that, any injector-invoked function can be injected with any provider-provided service--there is no restriction (other than theconfig
andrun
differences listed herein).
Adapted fromthis Stack Overflow answer