What "things" can be injected into others in Angular.js?
Asked Answered
H

3

142

I'm having a little hard time understanding Dependency Injection in Angular. So my question is, can anyone explain which of the "types", like Controller, Factory, Provider, etc can we inject into others, including other instances of same "type"?

What I'm actually looking for is this table filled with y/n. For cells with same row/column, that means injecting the value of one "type" into another another one with the same "type"

+----------------+----------+------------+-----------+---------+--------+----------+---------+-------+
| Can we inject? | Constant | Controller | Directive | Factory | Filter | Provider | Service | Value |
+----------------+----------+------------+-----------+---------+--------+----------+---------+-------+
| Constant       |          |            |           |         |        |          |         |       |
| Controller     |          |            |           |         |        |          |         |       |
| Directive      |          |            |           |         |        |          |         |       |
| Factory        |          |            |           |         |        |          |         |       |
| Filter         |          |            |           |         |        |          |         |       |
| Provider       |          |            |           |         |        |          |         |       |
| Service        |          |            |           |         |        |          |         |       |
| Value          |          |            |           |         |        |          |         |       |
+----------------+----------+------------+-----------+---------+--------+----------+---------+-------+
Hotel answered 30/5, 2013 at 5:2 Comment(1)
here is the response docs.angularjs.org/guide/providers#conclusionHeighttopaper
M
394

Rather that just fill in the table with "yes" and "no" with no explanation, I'll go into a little bit more detail.

[Note, added after finishing: this ended up being... quite a bit longer than I expected. There's a tl;dr at the bottom, but I hope this proves informational.]

[This answer has also been added to the AngularJS wiki: Understanding Dependency Injection]


The Provider ($provide)

The $provide service is responsible for telling Angular how to create new injectable things; these things are called services. Services are defined by things called providers, which is what you're creating when you use $provide. Defining a provider is done via the provider method on the $provide service, and you can get hold of the $provide service by asking for it to be injected into an application's config function. An example might be something like this:

app.config(function($provide) {
  $provide.provider('greeting', function() {
    this.$get = function() {
      return function(name) {
        alert("Hello, " + name);
      };
    };
  });
});

Here we've defined a new provider for a service called greeting; we can inject a variable named greeting 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 a name parameter and alerts a message based on the name. We might use it like this:

app.controller('MainController', function($scope, greeting) {
  $scope.onClick = function() {
    greeting('Ford Prefect');
  };
});

Now here's the trick. factory, service, and value 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 that exact same provider just like this:

app.config(function($provide) {
  $provide.factory('greeting', function() {
    return function(name) {
      alert("Hello, " + name);
    };
  });
});

It's important to understand, so I'll rephrase: under the hood, AngularJS is calling the exact 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 our factory function) is always exactly the same, we can write even less code using value. For example, since we always return the same function for our greeting service, we can use value to define it, too:

app.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 annoying app.config(function($provide) { ... }) thing I've been using. Since defining new providers (via any of the given methods above) is so common, AngularJS exposes the $provider methods directly on the module object, to save even more typing:

var myMod = angular.module('myModule', []);

myMod.provider("greeting", ...);
myMod.factory("greeting", ...);
myMod.value("greeting", ...);

These all do the same thing as the more verbose app.config(...) versions we used previously.

The one injectable I've skipped so far is constant. For now, it's easy enough to say that it works just like value. We'll see there's one difference later.

To review, all these pieces of code are doing the exact same thing:

myMod.provider('greeting', function() {
  this.$get = function() {
    return function(name) {
      alert("Hello, " + name);
    };
  };
});

myMod.factory('greeting', function() {
  return function(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 calling get on it with the name of the service. For example,

var greeting = $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's invoke method;

var myFunction = function(greeting) {
  greeting('Ford Prefect');
};
$injector.invoke(myFunction);

Its worth noting that the injector will only create an instance of a service once. 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, to answer your question, you can inject services into any function that is called with $injector.invoke. This includes

  • controller definition functions
  • directive definition functions
  • filter definition functions
  • the $get methods of providers (aka the factory definition functions)

Since constants and values always return a static value, they are not invoked via the injector, and thus you cannot inject them with anything.

Configuring Providers

You may be wondering why anyone would bother to set up a full-fledged provider with the provide method if factory, 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 I didn't mention is that these providers can be injected into config sections of your application so you can interact with them!

First, Angular runs your application in two-phases--the config and run phases. The config 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. The run 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 the myMod.config and myMod.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 that during the config phase, only providers can be injected (with the exception of the services in the AUTO module--$provide and $injector).

For example, the following is not 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 you do have access to are any providers for services you've made:

myMod.config(function(greetingProvider) {
  // a-ok!
});

There is one important exception: constants, since they cannot be changed, are allowed to be injected inside config blocks (this is how they differ from values). They are accessed by their name alone (no Provider suffix necessary).

Whenever you defined a provider for a service, that provider gets named serviceProvider, where service is the name of the service. Now we can use the power of providers do do some more complicated stuff!

myMod.provider('greeting', function() {
  var text = 'Hello, ';

  this.setText = function(value) {
    text = value;
  };

  this.$get = function() {
    return function(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 called setText that we can use to customize our alert; we can get access to this provider in a config block to call this method and customize the service. When we finally run our app, we can grab the greeting 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)

Controller functions can be injected into, but controllers themselves can't be injected into other 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 call myMod.controller(...), you're actually accessing this 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).

Filters and Directives

filter and directive work exactly the same way as controller; filter uses a service called $filter and its provider $filterProvider, while directive uses a service called $compile and its provider $compileProvider. Some links:

As per the other examples, myMod.filter and myMod.directive are shortcuts to configuring these services.


tl;dr

So, to summarize, any function that gets called with $injector.invoke can be injected into. This includes, from your chart (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 services that 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 can use those service 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 the config and run differences listed herein).

Modal answered 30/5, 2013 at 6:21 Comment(11)
Wow! thank you for taking time to answer in such detail! I've read this twice, and I think I've understood quite a bit. Gonna study it and the links you gave in detail later today. And another +1 for the cat. :)Hotel
You probably already answered it, but I have a question - does Angular always call the callback you pass in to provider, factory, etc, when they are injected into something, or does it cache things somewhere? Except constant maybe?Hotel
I'm glad you found it useful. Good call; I didn't mention it, but Angular only instantiates a service from a provider one time--then it caches it. I've updated the answer accordingly.Modal
One of the most useful and detailed SO answers I've come across - thanks!Saucepan
Great post. I follow right up to the discussion of $controllerProvider. If my controller code translates to a $controllerProvider method that configures $controller, why am I able to inject services into controllers? Don't we need to wait for run phase to inject services?Dignadignified
@david004 Thanks! The reason is that the dependencies aren't resolved during the config phase; the functions passed to $controllerProvider are saved off, and in the run phase $injector is used to invoke/instantiate them on an as-needed basis.Modal
This answer defines a new level of awesome. Illuminating stuff.Tabbitha
By far the best resource I have come across with for AngularJS. Thanks.Lapidate
Literally the best piece of AngularJS documentation I've seen. Way to go!Nicaea
I couldn't get a value to inject into my config. Then I read this, changed it to constant, and all is good!Skipton
This is one awesome explanation. Hats off to you @MichelleTilleyLinnealinnean
M
13

The point that BinaryMuse makes in her amazing answer about providers, factories, and services all being the same thing extremely important.

Below is an image that I think can visually illustrate her point:

AngularJS they are all just providers
(source: simplygoodcode.com)

Mg answered 19/11, 2015 at 20:20 Comment(0)
F
7

Great answer by Michelle. I just want to point out that directives can be injected. If you have a directive named myThing, you can inject it with myThingDirective: Here is a contrived example.

The above example is not very practical, however the ability to inject a directive is useful when you want to decorate that directive.

Franchescafranchise answered 19/5, 2014 at 0:40 Comment(1)
It seems that the second example to decorate that directive doesn't work since Angular 1.4. (see comment of Juan Biscaia there)Mease

© 2022 - 2024 — McMap. All rights reserved.