From 87ac100c666f3f189e1e7b57fbb71a15e3b25285 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 15 Apr 2015 22:35:38 +0000 Subject: [PATCH] docs: add DI to public docs --- gulpfile.js | 1 + modules/angular2/core.js | 2 +- modules/angular2/di.js | 1 + modules/angular2/di_annotations.js | 7 + modules/angular2/di_errors.js | 7 + modules/angular2/src/core/annotations/di.js | 6 +- modules/angular2/src/core/annotations/view.js | 55 ++-- .../src/core/life_cycle/life_cycle.js | 2 - modules/angular2/src/di/annotations.js | 27 +- modules/angular2/src/di/binding.js | 298 +++++++++++++++++- modules/angular2/src/di/exceptions.js | 75 ++++- modules/angular2/src/di/injector.js | 116 ++++++- modules/angular2/src/di/key.js | 44 ++- modules/angular2/src/di/opaque_token.js | 5 + modules/angular2/src/directives/switch.js | 2 - .../src/mock/template_resolver_mock.js | 12 +- modules/angular2/test/di/key_spec.js | 26 -- 17 files changed, 581 insertions(+), 105 deletions(-) create mode 100644 modules/angular2/di_annotations.js create mode 100644 modules/angular2/di_errors.js diff --git a/gulpfile.js b/gulpfile.js index 881ce83272..1d987656e7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -402,6 +402,7 @@ function createDocsTasks(public) { var dgeni = new Dgeni([require(dgeniPackage)]); return dgeni.generate(); } catch(x) { + console.log(x); console.log(x.stack); throw x; } diff --git a/modules/angular2/core.js b/modules/angular2/core.js index 8f1cb18d7f..4ed8c070e1 100644 --- a/modules/angular2/core.js +++ b/modules/angular2/core.js @@ -2,7 +2,7 @@ * @module * @public * @description - * Define public API for Angular here. + * Define angular core API here. */ export * from './src/core/annotations/visibility'; export * from './src/core/compiler/interfaces'; diff --git a/modules/angular2/di.js b/modules/angular2/di.js index ac0f07f165..7bab482ecf 100644 --- a/modules/angular2/di.js +++ b/modules/angular2/di.js @@ -1,5 +1,6 @@ /** * @module + * @public * @description * This is a description */ diff --git a/modules/angular2/di_annotations.js b/modules/angular2/di_annotations.js new file mode 100644 index 0000000000..c2aabbfc60 --- /dev/null +++ b/modules/angular2/di_annotations.js @@ -0,0 +1,7 @@ +/** + * @module + * @public + * @description + * This is a description + */ + diff --git a/modules/angular2/di_errors.js b/modules/angular2/di_errors.js new file mode 100644 index 0000000000..c2aabbfc60 --- /dev/null +++ b/modules/angular2/di_errors.js @@ -0,0 +1,7 @@ +/** + * @module + * @public + * @description + * This is a description + */ + diff --git a/modules/angular2/src/core/annotations/di.js b/modules/angular2/src/core/annotations/di.js index 2bcc97f0c9..975b7da942 100644 --- a/modules/angular2/src/core/annotations/di.js +++ b/modules/angular2/src/core/annotations/di.js @@ -30,13 +30,13 @@ export class PropertySetter extends DependencyAnnotation { * * ## Example * - * suppose we have an `` element and would like to know its `type`. + * Suppose we have an `` element and want to know its `type`. * * ```html * * ``` * - * A decorator could inject string literal `text` like so: + * A decorator can inject string literal `text` like so: * * ```javascript * @Decorator({ @@ -71,7 +71,7 @@ export class Attribute extends DependencyAnnotation { /** * Specifies that a [QueryList] should be injected. * - * See: [QueryList] for usage. + * See: [QueryList] for usage and example. * * @exportedAs angular2/annotations */ diff --git a/modules/angular2/src/core/annotations/view.js b/modules/angular2/src/core/annotations/view.js index 68fcea319e..30f5b2fb03 100644 --- a/modules/angular2/src/core/annotations/view.js +++ b/modules/angular2/src/core/annotations/view.js @@ -33,38 +33,57 @@ import {ABSTRACT, CONST, Type} from 'angular2/src/facade/lang'; * @exportedAs angular2/annotations */ 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; + + /** + * Specify an inline template for an angular component. + * + * NOTE: either `templateURL` or `template` should be used, but not both. + */ 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: ' + * ' + * }) + * class MyComponent { + * } + * ``` + */ directives:any; //List; - formatters:any; //List; - source:any;//List; - locale:any; //string - device:any; //string + @CONST() constructor({ templateUrl, template, - directives, - formatters, - source, - locale, - device + directives }: { templateUrl: string, template: string, - directives: List, - formatters: List, - source: List, - locale: string, - device: string + directives: List }) { this.templateUrl = templateUrl; this.template = template; this.directives = directives; - this.formatters = formatters; - this.source = source; - this.locale = locale; - this.device = device; } } diff --git a/modules/angular2/src/core/life_cycle/life_cycle.js b/modules/angular2/src/core/life_cycle/life_cycle.js index 2e08d5af19..b6fd82c7d1 100644 --- a/modules/angular2/src/core/life_cycle/life_cycle.js +++ b/modules/angular2/src/core/life_cycle/life_cycle.js @@ -26,8 +26,6 @@ import {isPresent} from 'angular2/src/facade/lang'; * lifecycle.tick(); * }); * ``` - * - * * @exportedAs angular2/change_detection */ @Injectable() diff --git a/modules/angular2/src/di/annotations.js b/modules/angular2/src/di/annotations.js index 1bc253637a..2a04a96153 100644 --- a/modules/angular2/src/di/annotations.js +++ b/modules/angular2/src/di/annotations.js @@ -5,10 +5,11 @@ import {CONST} from "angular2/src/facade/lang"; * * ``` * class AComponent { - * constructor(@Inject('aServiceToken') aService) {} + * constructor(@Inject(MyService) aService:MyService) {} * } * ``` * + * @exportedAs angular2/di_annotations */ export class Inject { token; @@ -23,12 +24,13 @@ export class Inject { * * ``` * class AComponent { - * constructor(@InjectPromise('aServiceToken') aServicePromise) { - * aServicePromise.then(aService => ...); + * constructor(@InjectPromise(MyService) aServicePromise:Promise) { + * aServicePromise.then(aService:MyService => ...); * } * } * ``` * + * @exportedAs angular2/di_annotations */ export class InjectPromise { token; @@ -43,12 +45,13 @@ export class InjectPromise { * * ``` * class AComponent { - * constructor(@InjectLazy('aServiceToken') aServiceFn) { - * aService = aServiceFn(); + * constructor(@InjectLazy(MyService) aServiceFn:Function) { + * var aService:MyService = aServiceFn(); * } * } * ``` * + * @exportedAs angular2/di_annotations */ export class InjectLazy { 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 { - * constructor(@Optional() dp:Dependency) { - * this.dp = dp; + * constructor(@Optional() aService:MyService) { + * this.aService = aService; * } * } * ``` * + * @exportedAs angular2/di_annotations */ export class Optional { @CONST() @@ -102,6 +105,7 @@ export class Optional { * The framework can use `new Parent()` to handle the `aService` dependency * in a specific way. * + * @exportedAs angular2/di_annotations */ export class DependencyAnnotation { @CONST() @@ -114,8 +118,8 @@ export class DependencyAnnotation { } /** - * A class annotation that marks a class as available to `Injector`s for - * creation. + * A marker annotation that marks a class as available to `Injector`s for creation. Used by tooling for generating + * constructor stubs. * * ``` * class NeedsService { @@ -125,6 +129,7 @@ export class DependencyAnnotation { * @Injectable * class UsefulService {} * ``` + * @exportedAs angular2/di_annotations */ export class Injectable { @CONST() diff --git a/modules/angular2/src/di/binding.js b/modules/angular2/src/di/binding.js index 10a284cf73..4cc995d139 100644 --- a/modules/angular2/src/di/binding.js +++ b/modules/angular2/src/di/binding.js @@ -5,6 +5,9 @@ import {Key} from './key'; import {Inject, InjectLazy, InjectPromise, Optional, DependencyAnnotation} from './annotations'; import {NoAnnotationError} from './exceptions'; +/** + * @private + */ export class Dependency { key:Key; asPromise:boolean; @@ -28,15 +31,168 @@ export class Dependency { 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 { + + /** + * Token used when retriving this binding. Usually the [Type]. + */ 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; + + /** + * Bind a key to a value. + * + * ## Example + * + * ```javascript + * var injector = Injector.resolveAndCreate([ + * new Binding(String, { toValue: 'Hello' }) + * ]); + * + * expect(injector.get(String)).toEqual('Hello'); + * ``` + */ 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; @CONST() @@ -59,6 +215,9 @@ export class Binding { this.dependencies = deps; } + /** + * + */ resolve(): ResolvedBinding { var factoryFn:Function; 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 { + /** + * A key, usually a [Type]. + */ key:Key; + + /** + * Factory function which can return an instance of [key]. + */ factory:Function; + + /** + * Arguments (dependencies) to the [factory] function. + */ dependencies:List; + + /** + * Specifies if the [factory] function returns an [Promise] + */ providedAsPromise:boolean; constructor(key:Key, factory:Function, dependencies:List, 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 { return new BindingBuilder(token); @@ -114,6 +295,7 @@ export function bind(token):BindingBuilder { /** * Helper class for [bind] function. + * @exportedAs angular2/di */ export class BindingBuilder { token; @@ -122,18 +304,107 @@ export class BindingBuilder { 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 { 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 { 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 { 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 { return new Binding(this.token, { 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 { return new Binding(this.token, { toAsyncFactory: factoryFunction, diff --git a/modules/angular2/src/di/exceptions.js b/modules/angular2/src/di/exceptions.js index c436d9f1e8..b31259377e 100644 --- a/modules/angular2/src/di/exceptions.js +++ b/modules/angular2/src/di/exceptions.js @@ -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 { keys:List; 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 { // TODO(tbosch): Can't do key:Key as this results in a circular dependency! 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 { // TODO(tbosch): Can't do key:Key as this results in a circular dependency! 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 { // TODO(tbosch): Can't do key:Key as this results in a circular dependency! 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 { // TODO(tbosch): Can't do key:Key as this results in a circular dependency! 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 { message:string; 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 { message:string; constructor(typeOrFunc) { diff --git a/modules/angular2/src/di/injector.js b/modules/angular2/src/di/injector.js index b5c00fed23..a51769b351 100644 --- a/modules/angular2/src/di/injector.js +++ b/modules/angular2/src/di/injector.js @@ -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 { _bindings:List; _instances:List; @@ -29,13 +67,16 @@ export class Injector { _syncStrategy:_SyncInjectorStrategy; /** - * Creates/looks up factory functions and dependencies from binding - * declarations and flattens bindings into a single [List]. + * Turns a list of binding definitions into internal resolved list of resolved 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]. + * A resolution is a process of flattening multiple nested lists and converting individual bindings into a + * list of [ResolvedBinding]s. The resolution can be cached for performance sensitive code. + * + * @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/**/):List { 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 - * corresponding [fromResolvedBindings] because it needs to resolve bindings. Prefer [fromResolvedBindings] - * in performance-critical code that creates lots of injectors. + * corresponding [fromResolvedBindings] because it needs to resolve bindings first. See [Injector.resolve]. + * + * 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/**/, {defaultBindings=false}={}) { return new Injector(Injector.resolve(bindings), null, defaultBindings); } /** - * Creates an injector from previously resolved bindings. This bypasses a lot - * of computation and is the recommended way to construct injectors in - * performance-sensitive parts. + * Creates an injector from previously resolved bindings. This bypasses resolution and flattening. This API is + * recommended way to construct injectors in 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, {defaultBindings=false}={}) { 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, parent:Injector, defaultBindings:boolean) { this._bindings = bindings; this._instances = this._createInstances(); @@ -70,22 +122,60 @@ export class Injector { 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) { + 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) { 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); } + /** + * 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/**/):Injector { 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):Injector { return new Injector(bindings, this, false); } @@ -283,7 +373,7 @@ function _resolveBindings(bindings:List): List { } else if (unresolved instanceof Type) { resolved = bind(unresolved).toClass(unresolved).resolve(); } else if (unresolved instanceof Binding) { - resolved = unresolved.resolve(); + resolved = unresolved.resolve(); } else if (unresolved instanceof List) { resolved = _resolveBindings(unresolved); } else if (unresolved instanceof BindingBuilder) { diff --git a/modules/angular2/src/di/key.js b/modules/angular2/src/di/key.js index 25704081c5..6d2c626b47 100644 --- a/modules/angular2/src/di/key.js +++ b/modules/angular2/src/di/key.js @@ -1,34 +1,48 @@ -import {KeyMetadataError} from './exceptions'; -import {MapWrapper, Map} from 'angular2/src/facade/collection'; -import {int, isPresent} from 'angular2/src/facade/lang'; +import {MapWrapper} from 'angular2/src/facade/collection'; +//import {int} 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 { token; - id:int; + id/* :int */; metadata:any; - constructor(token, id:int) { + constructor(token, id/* :int */) { this.token = token; this.id = id; this.metadata = null; } - static setMetadata(key:Key, metadata):Key { - if (isPresent(key.metadata) && key.metadata !== metadata) { - throw new KeyMetadataError(); - } - key.metadata = metadata; - return key; - } - + /** + * Retrieve a [Key] for a token. + */ static get(token):Key { return _globalKeyRegistry.get(token); } - static get numberOfKeys():int { + /** + * @returns number of [Key]s registered in the system. + */ + static get numberOfKeys()/* :int */ { return _globalKeyRegistry.numberOfKeys; } } +/** + * @private + */ export class KeyRegistry { _allKeys:Map; constructor() { @@ -47,7 +61,7 @@ export class KeyRegistry { return newKey; } - get numberOfKeys():int { + get numberOfKeys()/* :int */ { return MapWrapper.size(this._allKeys); } } diff --git a/modules/angular2/src/di/opaque_token.js b/modules/angular2/src/di/opaque_token.js index 9706bce696..e84f127a86 100644 --- a/modules/angular2/src/di/opaque_token.js +++ b/modules/angular2/src/di/opaque_token.js @@ -1,3 +1,8 @@ +/** + * + * + * @exportedAs angular2/di + */ export class OpaqueToken { _desc:string; diff --git a/modules/angular2/src/directives/switch.js b/modules/angular2/src/directives/switch.js index dc1ed4544f..22a1d80417 100644 --- a/modules/angular2/src/directives/switch.js +++ b/modules/angular2/src/directives/switch.js @@ -178,8 +178,6 @@ export class SwitchWhen { * * ``` * - * - * @exportedAs angular2/directives * ``` * * @exportedAs angular2/directives diff --git a/modules/angular2/src/mock/template_resolver_mock.js b/modules/angular2/src/mock/template_resolver_mock.js index dd71043f04..3a86598e79 100644 --- a/modules/angular2/src/mock/template_resolver_mock.js +++ b/modules/angular2/src/mock/template_resolver_mock.js @@ -94,11 +94,7 @@ export class MockTemplateResolver extends TemplateResolver { view = new View({ template: view.template, templateUrl: view.templateUrl, - directives: directives, - formatters: view.formatters, - source: view.source, - locale: view.locale, - device: view.device + directives: directives }); } @@ -107,11 +103,7 @@ export class MockTemplateResolver extends TemplateResolver { view = new View({ template: inlineTemplate, templateUrl: null, - directives: view.directives, - formatters: view.formatters, - source: view.source, - locale: view.locale, - device: view.device + directives: view.directives }); } diff --git a/modules/angular2/test/di/key_spec.js b/modules/angular2/test/di/key_spec.js index 1ecf54cad4..62f5cee561 100644 --- a/modules/angular2/test/di/key_spec.js +++ b/modules/angular2/test/di/key_spec.js @@ -21,31 +21,5 @@ export function main() { 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(); - }); - }); }); } \ No newline at end of file