docs(guide/di): clarify what can be injected into what

Also do some general housekeeping and tidying of the page.

Closes #6999
This commit is contained in:
Peter Bacon Darwin
2014-04-06 17:22:24 +01:00
parent cf8ed01c6e
commit fcfa6ebb6b

View File

@@ -4,30 +4,29 @@
# Dependency Injection
Dependency Injection (DI) is a software design pattern that deals with how code gets hold of its
dependencies.
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 service instantiation, resolution
of dependencies, and provision of dependencies to components as requested.
The Angular injector subsystem is in charge of creating components, resolving their dependencies,
and providing them to other components as requested.
For in-depth discussion about DI, see
[Dependency Injection](http://en.wikipedia.org/wiki/Dependency_injection) at Wikipedia,
[Inversion of Control](http://martinfowler.com/articles/injection.html) by Martin Fowler,
or read about DI in your favorite software design pattern book.
## DI in a nutshell
## DI in a Nutshell
There are only three ways an object or a function can get a hold of its dependencies:
1. The dependency can be created, typically using the `new` operator.
2. The dependency can be looked up by referring to a global variable.
3. The dependency can be passed in to where it is needed.
There are only three ways a component (object or function) can get a hold of its dependencies:
1. The component can create the dependency, typically using the `new` operator.
2. The component can look up the dependency, by referring to a global variable.
3. 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. 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.
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.
@@ -42,8 +41,8 @@ SomeClass.prototype.doSomething = function(name) {
}
```
In the above example `SomeClass` is not concerned with locating the `greeter` dependency, it
is simply handed the `greeter` at runtime.
In the above example `SomeClass` is not concerned with creating or locating the `greeter`
dependency, it is simply handed the `greeter` when it is instantiated.
This is desirable, but it puts the responsibility of getting hold of the dependency on the
code that constructs `SomeClass`.
@@ -51,74 +50,90 @@ code that constructs `SomeClass`.
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-module-injector.png">
To manage the responsibility of dependency creation, each Angular application has an {@link
angular.injector injector}. The injector is a service locator that is responsible for
angular.injector injector}. The injector is a
[service locator](http://en.wikipedia.org/wiki/Service_locator_pattern) that is responsible for
construction and lookup of dependencies.
Here is an example of using the injector service:
```js
// Provide the wiring information in a module
angular.module('myModule', []).
var myModule = angular.module('myModule', []);
```
// Teach the injector how to build a 'greeter'
// Notice that greeter itself is dependent on '$window'
factory('greeter', function($window) {
// This is a factory function, and is responsible for
// creating the 'greet' service.
return {
greet: function(text) {
$window.alert(text);
}
};
});
Teach the injector how to build a `greeter` service. Notice that `greeter` is dependent on the
`$window` service. The `greeter` service is an object that contains a `greet` method.
// New injector is created from the module.
// (This is usually done automatically by angular bootstrap)
```js
myModule.factory('greeter', function($window) {
return {
greet: function(text) {
$window.alert(text);
}
};
});
```
Create a new injector that can provide components defined in our `myModule` module and request our
`greeter` service from the injector. (This is usually done automatically by angular bootstrap).
```js
var injector = angular.injector(['myModule', 'ng']);
// Request any dependency from the injector
var greeter = injector.get('greeter');
```
Asking for dependencies solves the issue of hard coding, but it also means that the injector needs
to be passed throughout the application. Passing the injector breaks the [Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter). To remedy this, we turn the
dependency lookup responsibility to the injector by declaring the dependencies as in this example:
to be passed throughout the application. Passing the injector breaks the
[Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter). To remedy this, we use a declarative
notation in our HTML templates, to hand the responsibility of creating components over to the
injector, as in this example:
```html
<!-- Given this HTML -->
<div ng-controller="MyController">
<button ng-click="sayHello()">Hello</button>
</div>
```
```js
// And this controller definition
function MyController($scope, greeter) {
$scope.sayHello = function() {
greeter.greet('Hello World');
};
}
```
// The 'ng-controller' directive does this behind the scenes
When Angular compiles the HTML, it processes the `ng-controller` directive, which in turn
asks the injector to create an instance of the controller and its dependencies.
```js
injector.instantiate(MyController);
```
Notice that by having the `ng-controller` instantiate the class, it can satisfy all of the
dependencies of `MyController` without the controller ever knowing about the injector. This is
the best outcome. The application code simply asks for the dependencies it needs, without having to
deal with the injector. This setup does not break the Law of Demeter.
This is all done behinds the scenes. Notice that by having the `ng-controller` ask the injector to
instantiate the class, it can satisfy all of the dependencies of `MyController` without the
controller ever knowing about the injector.
This is the best outcome. The application code simply declares the dependencies it needs, without
having to deal with the injector. This setup does not break the Law of Demeter.
## Dependency Annotation
How does the injector know what service needs to be injected?
**How does the injector know what components need to be injected?**
The application developer needs to provide annotation information that the injector uses in order
to resolve the dependencies. Throughout Angular, certain API functions are invoked using the
injector, as per the API documentation. The injector needs to know what services to inject into
the function. Below are three equivalent ways of annotating your code with service name
information. These can be used interchangeably as you see fit and are equivalent.
the function. There are three equivalent ways of annotating your code with service name
information:
### Inferring Dependencies
- Implicitly from the function parameter names
- Using the `$inject` property annotation
- Using the inline array annotation
These can be used interchangeably as you see fit and are equivalent.
### Implicit Dependencies
The simplest way to get hold of the dependencies, is to assume that the function parameter names
are the names of the dependencies.
@@ -134,11 +149,12 @@ function declaration and extracting the parameter names. In the above example `$
`greeter` are two services which need to be injected into the function.
While straightforward, this method will not work with JavaScript minifiers/obfuscators as they
rename the method parameter names. This makes this way of annotating only useful for [pretotyping](http://www.pretotyping.org/), and demo applications.
rename the method parameter names. This makes this way of annotating only useful for
[pretotyping](http://www.pretotyping.org/), and demo applications.
### `$inject` Annotation
### `$inject` Property Annotation
To allow the minifers to rename the function parameters and still be able to inject right services
To allow the minifers to rename the function parameters and still be able to inject right services,
the function needs to be annotated with the `$inject` property. The `$inject` property is an array
of service names to inject.
@@ -149,18 +165,18 @@ var MyController = function(renamed$scope, renamedGreeter) {
MyController['$inject'] = ['$scope', 'greeter'];
```
In this scenario the ordering of the values in the '$inject' array must match the ordering of the arguments to inject.
Using above code snippet as an example, '$scope' will be injected into 'renamed$scope' and 'greeter' into 'renamedGreeter'.
Care must be taken that the `$inject` annotation is kept in sync with the actual arguments in the
function declaration.
In this scenario the ordering of the values in the `$inject` array must match the ordering of the
arguments to inject. Using above code snippet as an example, `$scope` will be injected into
`renamed$scope` and `greeter` into `renamedGreeter`. Care must be taken that the `$inject`
annotation is kept in sync with the actual arguments in the function declaration.
This method of annotation is useful for controller declarations since it assigns the annotation
information with the function.
### Inline Annotation
### Inline Array Annotation
Sometimes using the `$inject` annotation style is not convenient such as when annotating
directives.
directives or services defined inline by a factory function.
For example:
@@ -190,18 +206,69 @@ someModule.factory('greeter', ['$window', function(renamed$window) {
}]);
```
Here, instead of simply providing the factory function, we pass an array, whose elements consist of
a list of strings (the names of the depenendencies) followed by the function itself.
Keep in mind that all of the annotation styles are equivalent and can be used anywhere in Angular
where injection is supported.
## Where can I use DI?
## Where Can I Use DI?
DI is pervasive throughout Angular. You can use it in controllers, services, directives, filters,
animations, and `run` and `config` blocks.
DI is pervasive throughout Angular. You can use it when defining components or when providing `run`
and `config` blocks for a module.
### DI in controllers
- Components such as services, directives, filters and animations are defined by an injectable factory
method or constructor function. These components can be injected with "service" components as
dependencies.
Controllers are classes which are responsible for application behavior. The recommended way of
declaring controllers is using the array notation:
- The `run` and `config` methods accept a function, which can also be injected with "service"
components as dependencies.
- Controllers are defined by an constructor function, which can be injected with any of the "service"
components as dependencies, but they can also be provided with special dependencies. See "DI in
Controllers" below.
### Factory Methods
Factory methods are responsible for creating most objects in Angular. Examples are directives,
services, and filters. The factory methods are registered with the module, and the recommended way
of declaring factories is:
```js
angular.module('myModule', [])
.factory('serviceId', ['depService', function(depService) {
...
}])
.directive('directiveName', ['depService', function(depService) {
...
}])
.filter('filterName', ['depService', function(depService) {
...
}]);
```
### Module Methods
We can specify functions to run at configuration and run time for a module by calling the `run` and
`config` methods. These functions are injectable with dependencies just like the factory functions
above.
```js
angular.module('myModule', [])
.config(['depProvider', function(depProvider){
...
}])
.run(['depService', function(depService) {
...
}]);
```
### Controllers
Controllers are "classes" or "constructor functions" that are responsible for providing the
application behavior that supports the declarative markup in the template. The recommended way of
declaring Controllers is using the array notation:
```js
someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {
@@ -215,28 +282,14 @@ someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope
This avoids the creation of global functions for controllers and also protects against minification.
Controllers are special in that, unlike services, there can be many instances of them in the
application. For example, there would be one instance of a for every instance of the `ng-controller`
directive in the template.
### Factory methods
Moreover, additional dependencies are made available to Controllers:
Factory methods are responsible for creating most objects in Angular. Examples are directives,
services, and filters. The factory methods are registered with the module, and the recommended way
of declaring factories is:
```js
angular.module('myModule', []).
config(['depProvider', function(depProvider){
...
}]).
factory('serviceId', ['depService', function(depService) {
...
}]).
directive('directiveName', ['depService', function(depService) {
...
}]).
filter('filterName', ['depService', function(depService) {
...
}]).
run(['depService', function(depService) {
...
}]);
```
* {@link scope `$scope`}: Controllers are always associated with a point in the DOM and so are provided with
access to the {@link scope scope} at that point. Other components, such as servies only have access to the
singleton {@link $rootScope} service.
* {@link $route} resolves: If a controller is instantiated as part of a route, then any values that
are resolved as part of the route are made available for injection into the controller.