refactor: move angular source to /packages rather than modules/@angular

This commit is contained in:
Jason Aden
2017-03-02 10:48:42 -08:00
parent 5ad5301a3e
commit 3e51a19983
1051 changed files with 18 additions and 18 deletions

267
packages/docs/di/di.md Normal file
View File

@ -0,0 +1,267 @@
# Dependency Injection (DI): Documentation
This document describes in detail how the DI module works in Angular.
## Core Abstractions
The library is built on top of the following core abstractions: `Injector`, `Binding`, and `Dependency`.
* An injector is created from a set of bindings.
* An injector resolves dependencies and creates objects.
* 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.
* 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 {
constructor(@Inject(Engine) engine) {
}
}
var inj = Injector.resolveAndCreate([
bind(Car).toClass(Car),
bind(Engine).toClass(Engine)
]);
var car = inj.get(Car);
```
In this example we create two bindings: one for `Car` and one for `Engine`. `@Inject(Engine)` declares a dependency on Engine.
## Injector
An injector instantiates objects lazily, only when asked for, and then caches them.
Compare
```
var car = inj.get(Car); //instantiates both an Engine and a Car
```
with
```
var engine = inj.get(Engine); //instantiates an Engine
var car = inj.get(Car); //instantiates a Car (reuses Engine)
```
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
```
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.
## Child Injectors and Dependencies
Injectors are hierarchical.
```
var parent = Injector.resolveAndCreate([
bind(Engine).toClass(TurboEngine)
]);
var child = parent.resolveAndCreateChild([Car]);
var car = child.get(Car); // uses the Car binding from the child injector and Engine from the parent injector.
```
Injectors form a tree.
```
GrandParentInjector
/ \
Parent1Injector Parent2Injector
|
ChildInjector
```
The dependency resolution algorithm works as follows:
```
// this is pseudocode.
var inj = this;
while (inj) {
if (inj.hasKey(requestedKey)) {
return inj.get(requestedKey);
} else {
inj = inj.parent;
}
}
throw new NoProviderError(requestedKey);
```
So in the following example
```
class Car {
constructor(e: Engine){}
}
```
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.
### Constraints
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.
```
class Car {
constructor(@Self() e: Engine){}
}
```
A more realistic example is having two bindings that have to be provided together (e.g., NgModel and NgRequiredValidator.)
The `@Host` decorator tells DI to look for `Engine` in this injector, its parent, until it reaches a host (see the section on hosts.)
```
class Car {
constructor(@Host() e: Engine){}
}
```
The `@SkipSelf` decorator tells DI to look for `Engine` in the whole tree starting from the parent injector.
```
class Car {
constructor(@SkipSelf() e: Engine){}
}
```
### DI Does Not Walk Down
Dependency resolution only walks up the tree. The following will throw because DI will look for an instance of `Engine` starting from `parent`.
```
var parent = Injector.resolveAndCreate([Car]);
var child = parent.resolveAndCreateChild([
bind(Engine).toClass(TurboEngine)
]);
parent.get(Car); // will throw NoProviderError
```
## Bindings
You can bind to a class, a value, or a factory. It is also possible to alias existing bindings.
```
var inj = Injector.resolveAndCreate([
bind(Car).toClass(Car),
bind(Engine).toClass(Engine)
]);
var inj = Injector.resolveAndCreate([
Car, // syntax sugar for bind(Car).toClass(Car)
Engine
]);
var inj = Injector.resolveAndCreate([
bind(Car).toValue(new Car(new Engine()))
]);
var inj = Injector.resolveAndCreate([
bind(Car).toFactory((e) => new Car(e), [Engine]),
bind(Engine).toFactory(() => new Engine())
]);
```
You can bind any token.
```
var inj = Injector.resolveAndCreate([
bind(Car).toFactory((e) => new Car(), ["engine!"]),
bind("engine!").toClass(Engine)
]);
```
If you want to alias an existing binding, you can do so using `toAlias`:
```
var inj = Injector.resolveAndCreate([
bind(Engine).toClass(Engine),
bind("engine!").toAlias(Engine)
]);
```
which implies `inj.get(Engine) === inj.get("engine!")`.
Note that tokens and factory functions are decoupled.
```
bind("some token").toFactory(someFactory);
```
The `someFactory` function does not have to know that it creates an object for `some token`.
### Resolved Bindings
When DI receives `bind(Car).toClass(Car)`, it needs to do a few things before it can create an instance of `Car`:
- It needs to reflect on `Car` to create a factory function.
- It needs to normalize the dependencies (e.g., calculate lower and upper bounds).
The result of these two operations is a `ResolvedBinding`.
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.
You can create an injector using a list of resolved bindings.
```
var listOfResolvingProviders = Injector.resolve([Provider11, Provider2]);
var inj = Injector.fromResolvedProviders(listOfResolvingProviders);
inj.createChildFromResolvedProviders(listOfResolvedProviders);
```
### Transient Dependencies
An injector has only one instance created by each registered binding.
```
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.
We can create a child injector for each new instance:
```
var child = inj.resolveAndCreateChild([MyClass]);
child.get(MyClass);
```
Or we can register a factory function:
```
var inj = Injector.resolveAndCreate([
bind('MyClassFactory').toFactory(dep => () => new MyClass(dep), [SomeDependency])
]);
var factory = inj.get('MyClassFactory');
var instance1 = factory(), instance2 = factory();
// Depends on the implementation of MyClass, but generally holds.
expect(instance1).not.toBe(instance2);
```

View File

@ -0,0 +1,235 @@
# Dependency Injection (DI): Documentation (Advanced Topics)
This document talks about advanced topics related to the DI module and how it is used in Angular. You don't have to know this to use DI in Angular or independently.
### Key
Most of the time we do not have to deal with keys.
```
var inj = Injector.resolveAndCreate([
bind(Engine).toFactory(() => new TurboEngine()) //the passed in token Engine gets mapped to a key
]);
var engine = inj.get(Engine); //the passed in token Engine gets mapped to a key
```
Now, the same example, but with keys.
```
var ENGINE_KEY = Key.get(Engine);
var inj = Injector.resolveAndCreate([
bind(ENGINE_KEY).toFactory(() => new TurboEngine()) // no mapping
]);
var engine = inj.get(ENGINE_KEY); // no mapping
```
Every key has an id, which we utilize to store providers and instances. So Injector uses keys internally for performance reasons.
### ProtoInjector and Injector
Often there is a need to create multiple instances of essentially the same injector. In Angular, for example, every component element type gets an injector configured in the same way.
Doing the following would be very inefficient.
```
function createComponetInjector(parent, providers: Binding[]) {
return parent.resolveAndCreateChild(providers);
}
```
This would require us to resolve and store providers for every single component instance. Instead, we want to resolve and store the providers for every component type, and create a set of instances for every component. To enable that DI separates the meta information about injectables (providers and their dependencies), which are stored in `ProtoInjector`, and injectables themselves, which are stored in `Injector`.
```
var proto = new ProtoInjector(providers); // done once
function createComponentInjector(parent, proto) {
return new Injector(proto, parent);
}
```
`Injector.resolveAndCreate` creates both a `ProtoInjector` and an `Injector`.
### Host & Visibility
An injector can have a parent. The parent relationship can be marked as a "host" as follows:
```
var child = new Injector(proto, parent, true /* host */);
```
Hosts are used to constraint dependency resolution. For instance, in the following example, DI will stop looking for `Engine` after reaching the host.
```
class Car {
constructor(@Host() e: Engine) {}
}
```
Imagine the following scenario:
```
ParentInjector
/ \
/ \ host
Child1 Child2
```
Here both Child1 and Child2 are children of ParentInjector. Child2 marks this relationship as host. ParentInjector might want to expose two different sets of providers for its "regular" children and its "host" children. providers visible to "regular" children are called "public" and providers visible to "host" children are called "private". This is an advanced use case used by Angular, where components can provide different sets of providers for their children and their view.
Let's look at this example.
```
class Car {
constructor(@Host() e: Engine) {}
}
var parentProto = new ProtoInjector([
new BindingWithVisibility(Engine, Visibility.Public),
new BindingWithVisibility(Car, Visibility.Public)
]);
var parent = new Injector(parentProto);
var hostChildProto = new ProtoInjector([new BindingWithVisibility(Car, Visibility.Public)]);
var hostChild = new Injector(hostChildProto, parent, true);
var regularChildProto = new ProtoInjector([new BindingWithVisibility(Car, Visibility.Public)]);
var regularChild = new Injector(regularChildProto, parent, false);
hostChild.get(Car); // will throw because public dependencies declared at the host cannot be seen by child injectors
parent.get(Car); // this works
regularChild.get(Car); // this works
```
Now, let's mark `Engine` as private:
```
class Car {
constructor(@Host() e: Engine) {}
}
var parentProto = new ProtoInjector([
new BindingWithVisibility(Engine, Visibility.Private),
new BindingWithVisibility(Car, Visibility.Public)
]);
var parent = new Injector(parentProto);
var hostChildProto = new ProtoInjector([new BindingWithVisibility(Car, Visibility.Public)]);
var hostChild = new Injector(hostChildProto, parent, true);
var regularChildProto = new ProtoInjector([new BindingWithVisibility(Car, Visibility.Public)]);
var regularChild = new Injector(regularChildProto, parent, false);
hostChild.get(Car); // this works
parent.get(Car); // this throws
regularChild.get(Car); // this throws
```
Now, let's mark `Engine` as both public and private:
```
class Car {
constructor(@Host() e: Engine) {}
}
var parentProto = new ProtoInjector([
new BindingWithVisibility(Engine, Visibility.PublicAndPrivate),
new BindingWithVisibility(Car, Visibility.Public)
]);
var parent = new Injector(parentProto);
var hostChildProto = new ProtoInjector([new BindingWithVisibility(Car, Visibility.Public)]);
var hostChild = new Injector(hostChildProto, parent, true);
var regularChildProto = new ProtoInjector([new BindingWithVisibility(Car, Visibility.Public)]);
var regularChild = new Injector(regularChildProto, parent, false);
hostChild.get(Car); // this works
parent.get(Car); // this works
regularChild.get(Car); // this works
```
## Angular and DI
Now let's see how Angular uses DI behind the scenes.
The right mental model is to think that every DOM element has an Injector. (In practice, only interesting elements containing directives will have an injector, but this is a performance optimization)
There are two properties that can be used to configure DI: providers and viewProviders.
- `providers` affects the element and its children.
- `viewProviders` affects the component's view.
Every directive can declare injectables via `providers`, but only components can declare `viewProviders`.
Let's look at a complex example that shows how the injector tree gets created.
```
<my-component my-directive>
<needs-service></needs-service>
</my-component>
```
Both `MyComponent` and `MyDirective` are created on the same element.
```
@Component({
selector: 'my-component',
providers: [
bind('componentService').toValue('Host_MyComponentService')
],
viewProviders: [
bind('viewService').toValue('View_MyComponentService')
],
template: `<needs-view-service></needs-view-service>`,
directives: [NeedsViewService]
})
class MyComponent {}
@Directive({
selector: '[my-directive]',
providers: [
bind('directiveService').toValue('MyDirectiveService')
]
})
class MyDirective {
}
```
`NeedsService` and `NeedsViewService` look like this:
```
@Directive({
selector: 'needs-view-service'
})
class NeedsViewService {
constructor(@Host() @Inject('viewService') viewService) {}
}
@Directive({
selector: 'needs-service'
})
class NeedsService {
constructor(@Host() @Inject('componentService') service1,
@Host() @Inject('directiveService') service2) {}
}
```
This will create the following injector tree.
```
Injector1 [
{binding: MyComponent, visibility: Visibility.PublicAndPrivate},
{binding: 'componentService', visibility: Visibility.PublicAndPrivate},
{binding: 'viewService', visibility: Visibility.Private},
{binding: MyDirective visibility: Visibility.Public},
{binding: 'directiveService', visibility: Visibility.Public}
]
/ \
| \ host
Injector2 [ Injector3 [
{binding: NeedsService, visibility: Visibility.Public} {binding: NeedsViewService, visibility: Visibility.Public}
] ]
```
As you can see the component and its providers can be seen by its children and its view. The view providers can be seen only by the view. And the providers of other directives can be seen only their children.