docs: add DI to public docs

This commit is contained in:
Misko Hevery
2015-04-15 22:35:38 +00:00
parent 487c4d23c1
commit 87ac100c66
17 changed files with 581 additions and 105 deletions

View File

@ -402,6 +402,7 @@ function createDocsTasks(public) {
var dgeni = new Dgeni([require(dgeniPackage)]); var dgeni = new Dgeni([require(dgeniPackage)]);
return dgeni.generate(); return dgeni.generate();
} catch(x) { } catch(x) {
console.log(x);
console.log(x.stack); console.log(x.stack);
throw x; throw x;
} }

View File

@ -2,7 +2,7 @@
* @module * @module
* @public * @public
* @description * @description
* Define public API for Angular here. * Define angular core API here.
*/ */
export * from './src/core/annotations/visibility'; export * from './src/core/annotations/visibility';
export * from './src/core/compiler/interfaces'; export * from './src/core/compiler/interfaces';

View File

@ -1,5 +1,6 @@
/** /**
* @module * @module
* @public
* @description * @description
* This is a description * This is a description
*/ */

7
modules/angular2/di_annotations.js vendored Normal file
View File

@ -0,0 +1,7 @@
/**
* @module
* @public
* @description
* This is a description
*/

7
modules/angular2/di_errors.js vendored Normal file
View File

@ -0,0 +1,7 @@
/**
* @module
* @public
* @description
* This is a description
*/

View File

@ -30,13 +30,13 @@ export class PropertySetter extends DependencyAnnotation {
* *
* ## Example * ## Example
* *
* suppose we have an `<input>` element and would like to know its `type`. * Suppose we have an `<input>` element and want to know its `type`.
* *
* ```html * ```html
* <input type="text"> * <input type="text">
* ``` * ```
* *
* A decorator could inject string literal `text` like so: * A decorator can inject string literal `text` like so:
* *
* ```javascript * ```javascript
* @Decorator({ * @Decorator({
@ -71,7 +71,7 @@ export class Attribute extends DependencyAnnotation {
/** /**
* Specifies that a [QueryList] should be injected. * Specifies that a [QueryList] should be injected.
* *
* See: [QueryList] for usage. * See: [QueryList] for usage and example.
* *
* @exportedAs angular2/annotations * @exportedAs angular2/annotations
*/ */

View File

@ -33,38 +33,57 @@ import {ABSTRACT, CONST, Type} from 'angular2/src/facade/lang';
* @exportedAs angular2/annotations * @exportedAs angular2/annotations
*/ */
export class View { export class View {
/**
* Specify a template URL for an angular component.
*
* NOTE: either `templateURL` or `template` should be used, but not both.
*/
templateUrl:any; //string; templateUrl:any; //string;
/**
* Specify an inline template for an angular component.
*
* NOTE: either `templateURL` or `template` should be used, but not both.
*/
template:any; //string; template:any; //string;
/**
* Specify a list of directives that are active within a template. [TODO: true?]
*
* Directives must be listed explicitly to provide proper component encapsulation.
*
* ## Example
*
* ```javascript
* @Component({
* selector: 'my-component'
* })
* @View({
* directives: [For]
* template: '
* <ul>
* <li *for="item in items">{{item}}</li>
* </ul>'
* })
* class MyComponent {
* }
* ```
*/
directives:any; //List<Type>; directives:any; //List<Type>;
formatters:any; //List<Type>;
source:any;//List<View>;
locale:any; //string
device:any; //string
@CONST() @CONST()
constructor({ constructor({
templateUrl, templateUrl,
template, template,
directives, directives
formatters,
source,
locale,
device
}: { }: {
templateUrl: string, templateUrl: string,
template: string, template: string,
directives: List<Type>, directives: List<Type>
formatters: List<Type>,
source: List<View>,
locale: string,
device: string
}) })
{ {
this.templateUrl = templateUrl; this.templateUrl = templateUrl;
this.template = template; this.template = template;
this.directives = directives; this.directives = directives;
this.formatters = formatters;
this.source = source;
this.locale = locale;
this.device = device;
} }
} }

View File

@ -26,8 +26,6 @@ import {isPresent} from 'angular2/src/facade/lang';
* lifecycle.tick(); * lifecycle.tick();
* }); * });
* ``` * ```
*
*
* @exportedAs angular2/change_detection * @exportedAs angular2/change_detection
*/ */
@Injectable() @Injectable()

View File

@ -5,10 +5,11 @@ import {CONST} from "angular2/src/facade/lang";
* *
* ``` * ```
* class AComponent { * class AComponent {
* constructor(@Inject('aServiceToken') aService) {} * constructor(@Inject(MyService) aService:MyService) {}
* } * }
* ``` * ```
* *
* @exportedAs angular2/di_annotations
*/ */
export class Inject { export class Inject {
token; token;
@ -23,12 +24,13 @@ export class Inject {
* *
* ``` * ```
* class AComponent { * class AComponent {
* constructor(@InjectPromise('aServiceToken') aServicePromise) { * constructor(@InjectPromise(MyService) aServicePromise:Promise<MyService>) {
* aServicePromise.then(aService => ...); * aServicePromise.then(aService:MyService => ...);
* } * }
* } * }
* ``` * ```
* *
* @exportedAs angular2/di_annotations
*/ */
export class InjectPromise { export class InjectPromise {
token; token;
@ -43,12 +45,13 @@ export class InjectPromise {
* *
* ``` * ```
* class AComponent { * class AComponent {
* constructor(@InjectLazy('aServiceToken') aServiceFn) { * constructor(@InjectLazy(MyService) aServiceFn:Function) {
* aService = aServiceFn(); * var aService:MyService = aServiceFn();
* } * }
* } * }
* ``` * ```
* *
* @exportedAs angular2/di_annotations
*/ */
export class InjectLazy { export class InjectLazy {
token; token;
@ -59,16 +62,16 @@ export class InjectLazy {
} }
/** /**
* A parameter annotation that marks a dependency as optional. * A parameter annotation that marks a dependency as optional. (Injects `null` if not found.)
*
* ``` * ```
* class AComponent { * class AComponent {
* constructor(@Optional() dp:Dependency) { * constructor(@Optional() aService:MyService) {
* this.dp = dp; * this.aService = aService;
* } * }
* } * }
* ``` * ```
* *
* @exportedAs angular2/di_annotations
*/ */
export class Optional { export class Optional {
@CONST() @CONST()
@ -102,6 +105,7 @@ export class Optional {
* The framework can use `new Parent()` to handle the `aService` dependency * The framework can use `new Parent()` to handle the `aService` dependency
* in a specific way. * in a specific way.
* *
* @exportedAs angular2/di_annotations
*/ */
export class DependencyAnnotation { export class DependencyAnnotation {
@CONST() @CONST()
@ -114,8 +118,8 @@ export class DependencyAnnotation {
} }
/** /**
* A class annotation that marks a class as available to `Injector`s for * A marker annotation that marks a class as available to `Injector`s for creation. Used by tooling for generating
* creation. * constructor stubs.
* *
* ``` * ```
* class NeedsService { * class NeedsService {
@ -125,6 +129,7 @@ export class DependencyAnnotation {
* @Injectable * @Injectable
* class UsefulService {} * class UsefulService {}
* ``` * ```
* @exportedAs angular2/di_annotations
*/ */
export class Injectable { export class Injectable {
@CONST() @CONST()

View File

@ -5,6 +5,9 @@ import {Key} from './key';
import {Inject, InjectLazy, InjectPromise, Optional, DependencyAnnotation} from './annotations'; import {Inject, InjectLazy, InjectPromise, Optional, DependencyAnnotation} from './annotations';
import {NoAnnotationError} from './exceptions'; import {NoAnnotationError} from './exceptions';
/**
* @private
*/
export class Dependency { export class Dependency {
key:Key; key:Key;
asPromise:boolean; asPromise:boolean;
@ -28,15 +31,168 @@ export class Dependency {
var _EMPTY_LIST = []; // TODO: make const when supported var _EMPTY_LIST = []; // TODO: make const when supported
/** /**
* Declaration of a dependency binding. * Describes how the [Injector] should instantiate a given token.
*
* See [bind].
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* new Binding(String, { toValue: 'Hello' })
* ]);
*
* expect(injector.get(String)).toEqual('Hello');
* ```
*
* @exportedAs angular2/di
*/ */
export class Binding { export class Binding {
/**
* Token used when retriving this binding. Usually the [Type].
*/
token; token;
/**
* Bind an interface to an implementation / subclass.
*
* ## Example
*
* Becuse `toAlias` and `toClass` are often confused the example contains both use cases for easy comparison.
*
* ```javascript
*
* class Vehicle {}
*
* class Car extends Vehicle {}
*
* var injectorClass = Injector.resolveAndCreate([
* Car,
* new Binding(Vehicle, { toClass: Car })
* ]);
* var injectorAlias = Injector.resolveAndCreate([
* Car,
* new Binding(Vehicle, { toAlias: Car })
* ]);
*
* expect(injectorClass.get(Vehicle)).not.toBe(injectorClass.get(Car));
* expect(injectorClass.get(Vehicle) instanceof Car).toBe(true);
*
* expect(injectorAlias.get(Vehicle)).toBe(injectorAlias.get(Car));
* expect(injectorAlias.get(Vehicle) instanceof Car).toBe(true);
* ```
*/
toClass:Type; toClass:Type;
/**
* Bind a key to a value.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* new Binding(String, { toValue: 'Hello' })
* ]);
*
* expect(injector.get(String)).toEqual('Hello');
* ```
*/
toValue; toValue;
/**
* Bind a key to an alias of an existing key.
*
* An alias means that we will return the same instance as if the alias token was used. (This is in contrast to
* `toClass` where a separet instance of `toClass` will be returned.)
*
* ## Example
*
* Becuse `toAlias` and `toClass` are often confused the example contains both use cases for easy comparison.
*
* ```javascript
*
* class Vehicle {}
*
* class Car extends Vehicle {}
*
* var injectorAlias = Injector.resolveAndCreate([
* Car,
* new Binding(Vehicle, { toAlias: Car })
* ]);
* var injectorClass = Injector.resolveAndCreate([
* Car,
* new Binding(Vehicle, { toClass: Car })
* ]);
*
* expect(injectorAlias.get(Vehicle)).toBe(injectorAlias.get(Car));
* expect(injectorAlias.get(Vehicle) instanceof Car).toBe(true);
* expect(injectorClass.get(Vehicle)).not.toBe(injectorClass.get(Car));
* expect(injectorClass.get(Vehicle) instanceof Car).toBe(true);
* ```
*/
toAlias; toAlias;
/**
* Bind a key to a function which computes the value.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* new Binding(Number, { toFactory: () => { return 1+2; }}),
* new Binding(String, { toFactory: (value) => { return "Value: " + value; },
* dependencies: [String] })
* ]);
*
* expect(injector.get(Number)).toEqual(3);
* expect(injector.get(String)).toEqual('Value: 3');
* ```
*/
toFactory:Function; toFactory:Function;
/**
* Bind a key to a function which computes the value asynchronously.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* new Binding(Number, { toAsyncFactory: () => {
* return new Promise((resolve) => resolve(1 + 2));
* }}),
* new Binding(String, { toFactory: (value) => { return "Value: " + value; },
* dependencies: [String]})
* ]);
*
* injector.asyncGet(Number).then((v) => expect(v).toBe(3));
* injector.asyncGet(String).then((v) => expect(v).toBe('Value: 3'));
* ```
*
* The interesting thing to note is that event thougt `Numeber` has an async factory, the `String` factory
* function takes the resolved value. This shows that the [Injector] delays executing of the `String` factory
* until after the `Number` is resolved. This can only be done if the `token` is retrive
*/
toAsyncFactory:Function; toAsyncFactory:Function;
/**
* Used in conjunction with `toFactory` or `toAsyncFactory` and specifies the `token`s which should be injected
* into the factory function.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* new Binding(Number, { toFactory: () => { return 1+2; }}),
* new Binding(String, { toFactory: (value) => { return "Value: " + value; },
* dependencies: [String] })
* ]);
*
* expect(injector.get(Number)).toEqual(3);
* expect(injector.get(String)).toEqual('Value: 3');
* ```
*/
dependencies:List; dependencies:List;
@CONST() @CONST()
@ -59,6 +215,9 @@ export class Binding {
this.dependencies = deps; this.dependencies = deps;
} }
/**
*
*/
resolve(): ResolvedBinding { resolve(): ResolvedBinding {
var factoryFn:Function; var factoryFn:Function;
var resolvedDeps; var resolvedDeps;
@ -90,11 +249,31 @@ export class Binding {
} }
} }
/// Dependency binding with resolved keys and dependencies. /**
* An internal resolved representaion of a [Binding] used by [Injector].
*
* A [Binding] is resolved when it has a factory fonction. Binding to a class, alias, or value, are just convenience
* methods, as [Injector] only operates on calling factory functions.
*/
export class ResolvedBinding { export class ResolvedBinding {
/**
* A key, usually a [Type].
*/
key:Key; key:Key;
/**
* Factory function which can return an instance of [key].
*/
factory:Function; factory:Function;
/**
* Arguments (dependencies) to the [factory] function.
*/
dependencies:List<Dependency>; dependencies:List<Dependency>;
/**
* Specifies if the [factory] function returns an [Promise]
*/
providedAsPromise:boolean; providedAsPromise:boolean;
constructor(key:Key, factory:Function, dependencies:List<Dependency>, providedAsPromise:boolean) { constructor(key:Key, factory:Function, dependencies:List<Dependency>, providedAsPromise:boolean) {
@ -106,7 +285,9 @@ export class ResolvedBinding {
} }
/** /**
* Provides fluent API for imperative construction of [Binding] objects. * Provides fluent API for imperative construction of [Binding] objects. (JavaScript only.)
*
* @exportedAs angular2/di
*/ */
export function bind(token):BindingBuilder { export function bind(token):BindingBuilder {
return new BindingBuilder(token); return new BindingBuilder(token);
@ -114,6 +295,7 @@ export function bind(token):BindingBuilder {
/** /**
* Helper class for [bind] function. * Helper class for [bind] function.
* @exportedAs angular2/di
*/ */
export class BindingBuilder { export class BindingBuilder {
token; token;
@ -122,18 +304,107 @@ export class BindingBuilder {
this.token = token; this.token = token;
} }
/**
* Bind an interface to an implementation / subclass.
*
* ## Example
*
* Becuse `toAlias` and `toClass` are often confused the example contains both use cases for easy comparison.
*
* ```javascript
*
* class Vehicle {}
*
* class Car extends Vehicle {}
*
* var injectorClass = Injector.resolveAndCreate([
* Car,
* bind(Vehicle).toClass(Car)
* ]);
* var injectorAlias = Injector.resolveAndCreate([
* Car,
* bind(Vehicle).toAlias(Car)
* ]);
*
* expect(injectorClass.get(Vehicle)).not.toBe(injectorClass.get(Car));
* expect(injectorClass.get(Vehicle) instanceof Car).toBe(true);
*
* expect(injectorAlias.get(Vehicle)).toBe(injectorAlias.get(Car));
* expect(injectorAlias.get(Vehicle) instanceof Car).toBe(true);
* ```
*/
toClass(type:Type):Binding { toClass(type:Type):Binding {
return new Binding(this.token, {toClass: type}); return new Binding(this.token, {toClass: type});
} }
/**
* Bind a key to a value.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* bind(String).toValue('Hello')
* ]);
*
* expect(injector.get(String)).toEqual('Hello');
* ```
*/
toValue(value):Binding { toValue(value):Binding {
return new Binding(this.token, {toValue: value}); return new Binding(this.token, {toValue: value});
} }
/**
* Bind a key to an alias of an existing key.
*
* An alias means that we will return the same instance as if the alias token was used. (This is in contrast to
* `toClass` where a separet instance of `toClass` will be returned.)
*
* ## Example
*
* Becuse `toAlias` and `toClass` are often confused the example contains both use cases for easy comparison.
*
* ```javascript
*
* class Vehicle {}
*
* class Car extends Vehicle {}
*
* var injectorAlias = Injector.resolveAndCreate([
* Car,
* bind(Vehicle).toAlias(Car)
* ]);
* var injectorClass = Injector.resolveAndCreate([
* Car,
* bind(Vehicle).toClass(Car)
* ]);
*
* expect(injectorAlias.get(Vehicle)).toBe(injectorAlias.get(Car));
* expect(injectorAlias.get(Vehicle) instanceof Car).toBe(true);
* expect(injectorClass.get(Vehicle)).not.toBe(injectorClass.get(Car));
* expect(injectorClass.get(Vehicle) instanceof Car).toBe(true);
* ```
*/
toAlias(aliasToken):Binding { toAlias(aliasToken):Binding {
return new Binding(this.token, {toAlias: aliasToken}); return new Binding(this.token, {toAlias: aliasToken});
} }
/**
* Bind a key to a function which computes the value.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* bind(Number).toFactory(() => { return 1+2; }}),
* bind(String).toFactory((v) => { return "Value: " + v; }, [String] })
* ]);
*
* expect(injector.get(Number)).toEqual(3);
* expect(injector.get(String)).toEqual('Value: 3');
* ```
*/
toFactory(factoryFunction:Function, dependencies:List = null):Binding { toFactory(factoryFunction:Function, dependencies:List = null):Binding {
return new Binding(this.token, { return new Binding(this.token, {
toFactory: factoryFunction, toFactory: factoryFunction,
@ -141,6 +412,27 @@ export class BindingBuilder {
}); });
} }
/**
* Bind a key to a function which computes the value asynchronously.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* bind(Number).toAsyncFactory(() => {
* return new Promise((resolve) => resolve(1 + 2));
* }),
* bind(String).toFactory((v) => { return "Value: " + v; }, [String])
* ]);
*
* injector.asyncGet(Number).then((v) => expect(v).toBe(3));
* injector.asyncGet(String).then((v) => expect(v).toBe('Value: 3'));
* ```
*
* The interesting thing to note is that event thougt `Numeber` has an async factory, the `String` factory
* function takes the resolved value. This shows that the [Injector] delays executing of the `String` factory
* until after the `Number` is resolved. This can only be done if the `token` is retrive
*/
toAsyncFactory(factoryFunction:Function, dependencies:List = null):Binding { toAsyncFactory(factoryFunction:Function, dependencies:List = null):Binding {
return new Binding(this.token, { return new Binding(this.token, {
toAsyncFactory: factoryFunction, toAsyncFactory: factoryFunction,

View File

@ -24,8 +24,12 @@ function constructResolvingPath(keys:List) {
} }
} }
export class KeyMetadataError extends Error {}
/**
* Base class for all errors arising from missconfigured bindings.
*
* @exportedAs angular2/di_errors
*/
export class ProviderError extends Error { export class ProviderError extends Error {
keys:List; keys:List;
constructResolvingMessage:Function; constructResolvingMessage:Function;
@ -49,6 +53,12 @@ export class ProviderError extends Error {
} }
} }
/**
* Thrown when trying to retrieve a dependency by [Key] from [Injector], but [Injector] does not have a [Binding] for
* said [Key].
*
* @exportedAs angular2/di_errors
*/
export class NoProviderError extends ProviderError { export class NoProviderError extends ProviderError {
// TODO(tbosch): Can't do key:Key as this results in a circular dependency! // TODO(tbosch): Can't do key:Key as this results in a circular dependency!
constructor(key) { constructor(key) {
@ -59,6 +69,30 @@ export class NoProviderError extends ProviderError {
} }
} }
/**
* Throw when trying to retrieve async [Binding] using sync API.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* bind(Number).toAsyncFactory(() => {
* return new Promise((resolve) => resolve(1 + 2));
* }),
* bind(String).toFactory((v) => { return "Value: " + v; }, [String])
* ]);
*
* injector.asyncGet(String).then((v) => expect(v).toBe('Value: 3'));
* expect(() => {
* injector.get(String);
* }).toThrowError(AsycBindingError);
* ```
*
* The above example throws because `String` dependes no `Numeber` which is async. If any binding in the dependency
* graph is async then the graph can only be retrieved using `asyncGet` API.
*
* @exportedAs angular2/di_errors
*/
export class AsyncBindingError extends ProviderError { export class AsyncBindingError extends ProviderError {
// TODO(tbosch): Can't do key:Key as this results in a circular dependency! // TODO(tbosch): Can't do key:Key as this results in a circular dependency!
constructor(key) { constructor(key) {
@ -70,6 +104,24 @@ export class AsyncBindingError extends ProviderError {
} }
} }
/**
* Throw when dependencies from a cyle.
*
* ## Example:
*
* ```javascript
* class A {
* constructor(b:B) {}
* }
* class B {
* constructor(a:A) {}
* }
* ```
*
* Retrieving `A` or `B` will throw `CyclicDependencyError` as such a graph can not be constructed.
*
* @exportedAs angular2/di_errors
*/
export class CyclicDependencyError extends ProviderError { export class CyclicDependencyError extends ProviderError {
// TODO(tbosch): Can't do key:Key as this results in a circular dependency! // TODO(tbosch): Can't do key:Key as this results in a circular dependency!
constructor(key) { constructor(key) {
@ -79,6 +131,14 @@ export class CyclicDependencyError extends ProviderError {
} }
} }
/**
* Thrown when constructing type returns with an Error.
*
* The `InstantiationError` class contains the original error plus dependency graph which caused this object to be
* instantiated.
*
* @exportedAs angular2/di_errors
*/
export class InstantiationError extends ProviderError { export class InstantiationError extends ProviderError {
// TODO(tbosch): Can't do key:Key as this results in a circular dependency! // TODO(tbosch): Can't do key:Key as this results in a circular dependency!
constructor(originalException, key) { constructor(originalException, key) {
@ -90,6 +150,11 @@ export class InstantiationError extends ProviderError {
} }
} }
/**
* Thrown when object other then [Binding] (or [Type]) is passed to [Injector] creation.
*
* @exportedAs angular2/di_errors
*/
export class InvalidBindingError extends Error { export class InvalidBindingError extends Error {
message:string; message:string;
constructor(binding) { constructor(binding) {
@ -102,6 +167,14 @@ export class InvalidBindingError extends Error {
} }
} }
/**
* Thrown when the class as no annotation information.
*
* Lack of annotation prevents the [Injector] from determininig what dependencies need to be injected int the
* constructor.
*
* @exportedAs angular2/di_errors
*/
export class NoAnnotationError extends Error { export class NoAnnotationError extends Error {
message:string; message:string;
constructor(typeOrFunc) { constructor(typeOrFunc) {

View File

@ -20,6 +20,44 @@ function _isWaiting(obj):boolean {
} }
/**
* A dependency injection container used for resolving dependencies.
*
* An `Injector` is a replacement for a `new` operator, which can automatically resolve the constructor dependencies.
* In typical use, application code asks for the dependencies in the constructor and they are resolved by the
* `Injector`.
*
* ## Example:
*
* Suppose that we want to inject an `Engine` into class `Car`, we would define it like this:
*
* ```javascript
* class Engine {
* }
*
* class Car {
* constructor(@Inject(Engine) engine) {
* }
* }
*
* ```
*
* Next we need to write the code that creates and instantiates the `Injector`. We then ask for the `root` object,
* `Car`, so that the `Injector` can recursively build all of that object's dependencies.
*
* ```javascript
* main() {
* var injector = Injector.resolveAndCreate([Car, Engine]);
*
* // Get a reference to the `root` object, which will recursively instantiate the tree.
* var car = injector.get(Car);
* }
* ```
* Notice that we don't use the `new` operator because we explicitly want to have the `Injector` resolve all of the
* object's dependencies automatically.
*
* @exportedAs angular2/di
*/
export class Injector { export class Injector {
_bindings:List; _bindings:List;
_instances:List; _instances:List;
@ -29,13 +67,16 @@ export class Injector {
_syncStrategy:_SyncInjectorStrategy; _syncStrategy:_SyncInjectorStrategy;
/** /**
* Creates/looks up factory functions and dependencies from binding * Turns a list of binding definitions into internal resolved list of resolved bindings.
* declarations and flattens bindings into a single [List].
* *
* The returned list is sparse, indexed by [Key.id]. It is generally not * A resolution is a process of flattening multiple nested lists and converting individual bindings into a
* useful to application code other than for passing it to [Injector] * list of [ResolvedBinding]s. The resolution can be cached for performance sensitive code.
* functions that require resolved binding lists, such as *
* [fromResolvedBindings] and [createChildFromResolved]. * @param [bindings] can be a list of [Type], [Binding], [ResolvedBinding], or a recursive list of more bindings.
*
* The returned list is sparse, indexed by [Key.id]. It is generally not useful to application code other than for
* passing it to [Injector] functions that require resolved binding lists, such as [fromResolvedBindings] and
* [createChildFromResolved].
*/ */
static resolve(bindings:List/*<ResolvedBinding|Binding|Type|List>*/):List<ResolvedBinding> { static resolve(bindings:List/*<ResolvedBinding|Binding|Type|List>*/):List<ResolvedBinding> {
var resolvedBindings = _resolveBindings(bindings); var resolvedBindings = _resolveBindings(bindings);
@ -45,22 +86,33 @@ export class Injector {
/** /**
* Resolves bindings and creates an injector based on those bindings. This function is slower than the * Resolves bindings and creates an injector based on those bindings. This function is slower than the
* corresponding [fromResolvedBindings] because it needs to resolve bindings. Prefer [fromResolvedBindings] * corresponding [fromResolvedBindings] because it needs to resolve bindings first. See [Injector.resolve].
* in performance-critical code that creates lots of injectors. *
* Prefer [fromResolvedBindings] in performance-critical code that creates lots of injectors.
*
* @param [bindings] can be a list of [Type], [Binding], [ResolvedBinding], or a recursive list of more bindings.
* @param [defaultBindings] Setting to true will auto-create bindings.
*/ */
static resolveAndCreate(bindings:List/*<ResolvedBinding|Binding|Type|List>*/, {defaultBindings=false}={}) { static resolveAndCreate(bindings:List/*<ResolvedBinding|Binding|Type|List>*/, {defaultBindings=false}={}) {
return new Injector(Injector.resolve(bindings), null, defaultBindings); return new Injector(Injector.resolve(bindings), null, defaultBindings);
} }
/** /**
* Creates an injector from previously resolved bindings. This bypasses a lot * Creates an injector from previously resolved bindings. This bypasses resolution and flattening. This API is
* of computation and is the recommended way to construct injectors in * recommended way to construct injectors in performance-sensitive parts.
* performance-sensitive parts. *
* @param [bindings] A sparse list of [ResolvedBinding]s. See [Injector.resolve].
* @param [defaultBindings] Setting to true will auto-create bindings.
*/ */
static fromResolvedBindings(bindings:List<ResolvedBinding>, {defaultBindings=false}={}) { static fromResolvedBindings(bindings:List<ResolvedBinding>, {defaultBindings=false}={}) {
return new Injector(bindings, null, defaultBindings); return new Injector(bindings, null, defaultBindings);
} }
/**
* @param [bindings] A sparse list of [ResolvedBinding]s. See [Injector.resolve].
* @param [parent] Parent Injector or `null` if root injector.
* @param [defaultBindings] Setting to true will auto-create bindings. (Only use with root injector.)
*/
constructor(bindings:List<ResolvedBinding>, parent:Injector, defaultBindings:boolean) { constructor(bindings:List<ResolvedBinding>, parent:Injector, defaultBindings:boolean) {
this._bindings = bindings; this._bindings = bindings;
this._instances = this._createInstances(); this._instances = this._createInstances();
@ -70,22 +122,60 @@ export class Injector {
this._syncStrategy = new _SyncInjectorStrategy(this); this._syncStrategy = new _SyncInjectorStrategy(this);
} }
/**
* Used to retrieve an instance from the injector.
*
* @param [token] usually the [Type] of object. (Same as token used while setting up a binding).
* @returns an instance represented by the token. Throws if not found.
*/
get(token) { get(token) {
return this._getByKey(Key.get(token), false, false, false); return this._getByKey(Key.get(token), false, false, false);
} }
/**
* Used to retrieve an instance from the injector.
*
* @param [token] usually the [Type] of object. (Same as token used while setting up a binding).
* @returns an instance represented by the token. Returns `null` if not found.
*/
getOptional(token) { getOptional(token) {
return this._getByKey(Key.get(token), false, false, true); return this._getByKey(Key.get(token), false, false, true);
} }
asyncGet(token) { /**
* Used to retrieve an instance from the injector asynchronously. Used with asynchronous bindings.
*
* @param [token] usually the [Type] of object. (Same as token used while setting up a binding).
* @returns a [Promise] which resolves to the instance represented by the token.
*/
asyncGet(token):Promise {
return this._getByKey(Key.get(token), true, false, false); return this._getByKey(Key.get(token), true, false, false);
} }
/**
* Create a child injector and load a new set of bindings into it.
*
* A resolution is a process of flattening multiple nested and converting individual bindings into a
* list of [ResolvedBinding]s. The resolution can be cached [Injector.resolve] for performance sensitive
* code.
*
* See: [Injector.resolve].
*
* @param [bindings] can be a list of [Type], [Binding], [ResolvedBinding], or a recursive list of more bindings.
* @returns a new child `Injector`.
*/
resolveAndCreateChild(bindings:List/*<ResolvedBinding|Binding|Type|List>*/):Injector { resolveAndCreateChild(bindings:List/*<ResolvedBinding|Binding|Type|List>*/):Injector {
return new Injector(Injector.resolve(bindings), this, false); return new Injector(Injector.resolve(bindings), this, false);
} }
/**
* Create a child injector and load a new set of [ResolvedBinding] into it.
*
* @param [bindings] A sparse list of [ResolvedBinding]s. See [Injector.resolve].
* @returns a new child `Injector`.
*/
createChildFromResolved(bindings:List<ResolvedBinding>):Injector { createChildFromResolved(bindings:List<ResolvedBinding>):Injector {
return new Injector(bindings, this, false); return new Injector(bindings, this, false);
} }
@ -283,7 +373,7 @@ function _resolveBindings(bindings:List): List {
} else if (unresolved instanceof Type) { } else if (unresolved instanceof Type) {
resolved = bind(unresolved).toClass(unresolved).resolve(); resolved = bind(unresolved).toClass(unresolved).resolve();
} else if (unresolved instanceof Binding) { } else if (unresolved instanceof Binding) {
resolved = unresolved.resolve(); resolved = unresolved.resolve();
} else if (unresolved instanceof List) { } else if (unresolved instanceof List) {
resolved = _resolveBindings(unresolved); resolved = _resolveBindings(unresolved);
} else if (unresolved instanceof BindingBuilder) { } else if (unresolved instanceof BindingBuilder) {

View File

@ -1,34 +1,48 @@
import {KeyMetadataError} from './exceptions'; import {MapWrapper} from 'angular2/src/facade/collection';
import {MapWrapper, Map} from 'angular2/src/facade/collection'; //import {int} from 'angular2/src/facade/lang';
import {int, isPresent} from 'angular2/src/facade/lang';
// TODO: uncoment `int` once https://github.com/angular/angular/issues/1414 is fixed
/**
* A unique object used for retrieving items from the Injector.
*
* [Key]s have:
* - system wide unique [id].
* - [token] usually the [Type] of the instance.
*
* [Key]s are used internaly in [Injector] becouse they have system wide unique [id]s which allow the injector to
* index in arrays rather ther look up items in maps.
*
* @exportedAs angular2/di
*/
export class Key { export class Key {
token; token;
id:int; id/* :int */;
metadata:any; metadata:any;
constructor(token, id:int) { constructor(token, id/* :int */) {
this.token = token; this.token = token;
this.id = id; this.id = id;
this.metadata = null; this.metadata = null;
} }
static setMetadata(key:Key, metadata):Key { /**
if (isPresent(key.metadata) && key.metadata !== metadata) { * Retrieve a [Key] for a token.
throw new KeyMetadataError(); */
}
key.metadata = metadata;
return key;
}
static get(token):Key { static get(token):Key {
return _globalKeyRegistry.get(token); return _globalKeyRegistry.get(token);
} }
static get numberOfKeys():int { /**
* @returns number of [Key]s registered in the system.
*/
static get numberOfKeys()/* :int */ {
return _globalKeyRegistry.numberOfKeys; return _globalKeyRegistry.numberOfKeys;
} }
} }
/**
* @private
*/
export class KeyRegistry { export class KeyRegistry {
_allKeys:Map; _allKeys:Map;
constructor() { constructor() {
@ -47,7 +61,7 @@ export class KeyRegistry {
return newKey; return newKey;
} }
get numberOfKeys():int { get numberOfKeys()/* :int */ {
return MapWrapper.size(this._allKeys); return MapWrapper.size(this._allKeys);
} }
} }

View File

@ -1,3 +1,8 @@
/**
*
*
* @exportedAs angular2/di
*/
export class OpaqueToken { export class OpaqueToken {
_desc:string; _desc:string;

View File

@ -178,8 +178,6 @@ export class SwitchWhen {
* *
* ``` * ```
* <template [switch-default]>...</template> * <template [switch-default]>...</template>
*
* @exportedAs angular2/directives
* ``` * ```
* *
* @exportedAs angular2/directives * @exportedAs angular2/directives

View File

@ -94,11 +94,7 @@ export class MockTemplateResolver extends TemplateResolver {
view = new View({ view = new View({
template: view.template, template: view.template,
templateUrl: view.templateUrl, templateUrl: view.templateUrl,
directives: directives, directives: directives
formatters: view.formatters,
source: view.source,
locale: view.locale,
device: view.device
}); });
} }
@ -107,11 +103,7 @@ export class MockTemplateResolver extends TemplateResolver {
view = new View({ view = new View({
template: inlineTemplate, template: inlineTemplate,
templateUrl: null, templateUrl: null,
directives: view.directives, directives: view.directives
formatters: view.formatters,
source: view.source,
locale: view.locale,
device: view.device
}); });
} }

View File

@ -21,31 +21,5 @@ export function main() {
expect(registry.get(registry.get('car'))).toBe(registry.get('car')); expect(registry.get(registry.get('car'))).toBe(registry.get('car'));
}); });
describe("metadata", function () {
it("should assign metadata to a key", function () {
var key = registry.get('car');
Key.setMetadata(key, "meta");
expect(key.metadata).toEqual("meta");
});
it("should allow assigning the same metadata twice", function () {
var key = registry.get('car');
Key.setMetadata(key, "meta");
Key.setMetadata(key, "meta");
expect(key.metadata).toEqual("meta");
});
it("should throw when assigning different metadata", function () {
var key = registry.get('car');
Key.setMetadata(key, "meta1");
expect(() => Key.setMetadata(key, "meta2")).toThrowError();
});
});
}); });
} }