268 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # Dependency Injection (DI): Documentation
 | |
| 
 | |
| This document describes in detail how the DI module works in Angular 2.
 | |
| 
 | |
| ## 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 NoBindingError(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 NoBindingError
 | |
| ```
 | |
| 
 | |
| 
 | |
| ## 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 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 listOfResolvingBindings = Injector.resolve([Binding1, Binding2]);
 | |
| var inj = Injector.fromResolvedBindings(listOfResolvingBindings);
 | |
| inj.createChildFromResolvedBindings(listOfResolvedBindings);
 | |
| ```
 | |
| 
 | |
| 
 | |
| ### 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);
 | |
| ```
 |