angular-cn/packages/docs/di/di.md

268 lines
7.0 KiB
Markdown
Raw Normal View History

2015-07-29 19:32:09 -04:00
# Dependency Injection (DI): Documentation
2014-10-09 09:44:18 -04:00
This document describes in detail how the DI module works in Angular.
2014-10-09 09:44:18 -04:00
## Core Abstractions
The library is built on top of the following core abstractions: `Injector`, `Binding`, and `Dependency`.
2015-07-29 19:32:09 -04:00
* An injector is created from a set of bindings.
2014-10-09 09:44:18 -04:00
* An injector resolves dependencies and creates objects.
2015-07-29 19:32:09 -04:00
* A binding maps a token, such as a string or class, to a factory function and a list of dependencies. So a binding defines how to create an object.
2014-10-09 09:44:18 -04:00
* A dependency points to a token and contains extra information on how the object corresponding to that token should be injected.
```
[Injector]
|
|
|*
[Binding]
|----------|-----------------|
| | |*
[Token] [FactoryFn] [Dependency]
|---------|
| |
[Token] [Flags]
```
## Example
```
class Engine {
}
class Car {
2015-08-12 19:34:13 -04:00
constructor(@Inject(Engine) engine) {
}
2014-10-09 09:44:18 -04:00
}
var inj = Injector.resolveAndCreate([
2015-08-12 19:34:13 -04:00
bind(Car).toClass(Car),
bind(Engine).toClass(Engine)
2014-10-09 09:44:18 -04:00
]);
var car = inj.get(Car);
```
2015-08-12 19:34:13 -04:00
In this example we create two bindings: one for `Car` and one for `Engine`. `@Inject(Engine)` declares a dependency on Engine.
2014-10-09 09:44:18 -04:00
## Injector
2015-07-29 19:32:09 -04:00
An injector instantiates objects lazily, only when asked for, and then caches them.
2014-10-09 09:44:18 -04:00
Compare
```
var car = inj.get(Car); //instantiates both an Engine and a Car
```
with
```
var engine = inj.get(Engine); //instantiates an Engine
2015-07-29 19:32:09 -04:00
var car = inj.get(Car); //instantiates a Car (reuses Engine)
2014-10-09 09:44:18 -04:00
```
and with
```
var car = inj.get(Car); //instantiates both an Engine and a Car
var engine = inj.get(Engine); //reads the Engine from the cache
```
2015-07-29 19:32:09 -04:00
To avoid bugs make sure the registered objects have side-effect-free constructors. In this case, an injector acts like a hash map, where the order in which the objects got created does not matter.
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
## Child Injectors and Dependencies
2014-10-09 09:44:18 -04:00
Injectors are hierarchical.
```
2015-07-29 19:32:09 -04:00
var parent = Injector.resolveAndCreate([
2015-08-12 19:34:13 -04:00
bind(Engine).toClass(TurboEngine)
2014-10-09 09:44:18 -04:00
]);
2015-07-29 19:32:09 -04:00
var child = parent.resolveAndCreateChild([Car]);
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
var car = child.get(Car); // uses the Car binding from the child injector and Engine from the parent injector.
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
Injectors form a tree.
2014-10-09 09:44:18 -04:00
```
2015-08-12 19:34:13 -04:00
GrandParentInjector
/ \
2015-07-29 19:32:09 -04:00
Parent1Injector Parent2Injector
|
ChildInjector
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
The dependency resolution algorithm works as follows:
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
// this is pseudocode.
var inj = this;
while (inj) {
if (inj.hasKey(requestedKey)) {
2015-08-12 19:34:13 -04:00
return inj.get(requestedKey);
} else {
inj = inj.parent;
}
2015-07-29 19:32:09 -04:00
}
throw new NoProviderError(requestedKey);
```
2015-07-29 19:32:09 -04:00
So in the following example
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
class Car {
constructor(e: Engine){}
}
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
DI will start resolving `Engine` in the same injector where the `Car` binding is defined. It will check whether that injector has the `Engine` binding. If it is the case, it will return that instance. If not, the injector will ask its parent whether it has an instance of `Engine`. The process continues until either an instance of `Engine` has been found, or we have reached the root of the injector tree.
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
### Constraints
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
You can put upper and lower bound constraints on a dependency. For instance, the `@Self` decorator tells DI to look for `Engine` only in the same injector where `Car` is defined. So it will not walk up the tree.
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
class Car {
constructor(@Self() e: Engine){}
}
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
A more realistic example is having two bindings that have to be provided together (e.g., NgModel and NgRequiredValidator.)
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
The `@Host` decorator tells DI to look for `Engine` in this injector, its parent, until it reaches a host (see the section on hosts.)
2014-10-09 09:44:18 -04:00
```
class Car {
2015-07-29 19:32:09 -04:00
constructor(@Host() e: Engine){}
2014-10-09 09:44:18 -04:00
}
2015-07-29 19:32:09 -04:00
```
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
The `@SkipSelf` decorator tells DI to look for `Engine` in the whole tree starting from the parent injector.
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
```
2014-10-09 09:44:18 -04:00
class Car {
2015-07-29 19:32:09 -04:00
constructor(@SkipSelf() e: Engine){}
2014-10-09 09:44:18 -04:00
}
```
2015-07-29 19:32:09 -04:00
### DI Does Not Walk Down
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
Dependency resolution only walks up the tree. The following will throw because DI will look for an instance of `Engine` starting from `parent`.
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
var parent = Injector.resolveAndCreate([Car]);
2015-08-12 19:34:13 -04:00
var child = parent.resolveAndCreateChild([
2015-07-29 19:32:09 -04:00
bind(Engine).toClass(TurboEngine)
]);
2014-10-09 09:44:18 -04:00
parent.get(Car); // will throw NoProviderError
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
## Bindings
You can bind to a class, a value, or a factory. It is also possible to alias existing bindings.
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
var inj = Injector.resolveAndCreate([
2015-08-12 19:34:13 -04:00
bind(Car).toClass(Car),
bind(Engine).toClass(Engine)
2015-07-29 19:32:09 -04:00
]);
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
var inj = Injector.resolveAndCreate([
2015-08-12 19:34:13 -04:00
Car, // syntax sugar for bind(Car).toClass(Car)
Engine
2015-07-29 19:32:09 -04:00
]);
2014-10-09 09:44:18 -04:00
var inj = Injector.resolveAndCreate([
2015-08-12 19:34:13 -04:00
bind(Car).toValue(new Car(new Engine()))
2015-07-29 19:32:09 -04:00
]);
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
var inj = Injector.resolveAndCreate([
2015-08-12 19:34:13 -04:00
bind(Car).toFactory((e) => new Car(e), [Engine]),
bind(Engine).toFactory(() => new Engine())
2015-07-29 19:32:09 -04:00
]);
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
You can bind any token.
2014-10-09 09:44:18 -04:00
```
var inj = Injector.resolveAndCreate([
2015-08-12 19:34:13 -04:00
bind(Car).toFactory((e) => new Car(), ["engine!"]),
bind("engine!").toClass(Engine)
2015-07-29 19:32:09 -04:00
]);
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
If you want to alias an existing binding, you can do so using `toAlias`:
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
var inj = Injector.resolveAndCreate([
2015-08-12 19:34:13 -04:00
bind(Engine).toClass(Engine),
bind("engine!").toAlias(Engine)
2015-07-29 19:32:09 -04:00
]);
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
which implies `inj.get(Engine) === inj.get("engine!")`.
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
Note that tokens and factory functions are decoupled.
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
bind("some token").toFactory(someFactory);
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
The `someFactory` function does not have to know that it creates an object for `some token`.
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
### Resolved Bindings
2014-10-09 09:44:18 -04:00
When DI receives `bind(Car).toClass(Car)`, it needs to do a few things before it can create an instance of `Car`:
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
- It needs to reflect on `Car` to create a factory function.
- It needs to normalize the dependencies (e.g., calculate lower and upper bounds).
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
The result of these two operations is a `ResolvedBinding`.
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
The `resolveAndCreate` and `resolveAndCreateChild` functions resolve passed-in bindings before creating an injector. But you can resolve bindings yourself using `Injector.resolve([bind(Car).toClass(Car)])`. Creating an injector from pre-resolved bindings is faster, and may be needed for performance sensitive areas.
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
You can create an injector using a list of resolved bindings.
2015-07-29 19:32:09 -04:00
```
var listOfResolvingProviders = Injector.resolve([Provider11, Provider2]);
var inj = Injector.fromResolvedProviders(listOfResolvingProviders);
inj.createChildFromResolvedProviders(listOfResolvedProviders);
2014-10-09 09:44:18 -04:00
```
2015-07-29 19:32:09 -04:00
### Transient Dependencies
2014-10-09 09:44:18 -04:00
2015-07-29 19:32:09 -04:00
An injector has only one instance created by each registered binding.
2014-10-09 09:44:18 -04:00
```
inj.get(MyClass) === inj.get(MyClass); //always holds
```
If we need a transient dependency, something that we want a new instance of every single time, we have two options.
2015-07-29 19:32:09 -04:00
We can create a child injector for each new instance:
2014-10-09 09:44:18 -04:00
```
var child = inj.resolveAndCreateChild([MyClass]);
2014-10-09 09:44:18 -04:00
child.get(MyClass);
```
Or we can register a factory function:
```
var inj = Injector.resolveAndCreate([
bind('MyClassFactory').toFactory(dep => () => new MyClass(dep), [SomeDependency])
2014-10-09 09:44:18 -04:00
]);
var factory = inj.get('MyClassFactory');
var instance1 = factory(), instance2 = factory();
// Depends on the implementation of MyClass, but generally holds.
expect(instance1).not.toBe(instance2);
2014-10-09 09:44:18 -04:00
```