refactor(di): unified di injector and core injector

BREAKING CHANGES:

* InjectAsync and InjectLazy have been removed
* toAsyncFactory has been removed
This commit is contained in:
vsavkin 2015-06-26 15:59:18 -07:00
parent b688dee4c8
commit 22d3943831
49 changed files with 1211 additions and 1669 deletions

View File

@ -22,9 +22,8 @@ export {Form} from './src/forms/directives/form_interface';
export {TypeDecorator, ClassDefinition} from './src/util/decorators';
export {Query} from './src/core/annotations_impl/di';
export {ControlContainer} from './src/forms/directives/control_container';
export {Injectable} from './src/di/annotations_impl';
export {Injectable, Visibility} from './src/di/annotations_impl';
export {BaseQueryList} from './src/core/compiler/base_query_list';
export {AppProtoView, AppView, AppViewContainer} from './src/core/compiler/view';
export * from './src/change_detection/parser/ast';
export {Visibility} from './src/core/annotations_impl/visibility';
export {AppViewManager} from './src/core/compiler/view_manager';

View File

@ -31,14 +31,13 @@ export {
export {
Inject,
InjectPromise,
InjectLazy,
Optional,
Injectable,
forwardRef,
resolveForwardRef,
ForwardRefFn,
Injector,
ProtoInjector,
Binding,
bind,
Key,
@ -52,7 +51,12 @@ export {
OpaqueToken,
ResolvedBinding,
BindingBuilder,
Dependency
Dependency,
Visibility,
Self,
Parent,
Ancestor,
Unbounded
} from './di';
export * from './core';

View File

@ -4,7 +4,6 @@
* @description
* Define angular core API here.
*/
export * from './src/core/annotations/visibility';
export * from './src/core/annotations/view';
export * from './src/core/application';
export * from './src/core/application_tokens';

View File

@ -7,16 +7,39 @@
export {
InjectAnnotation,
InjectPromiseAnnotation,
InjectLazyAnnotation,
OptionalAnnotation,
InjectableAnnotation,
DependencyAnnotation
DependencyAnnotation,
VisibilityAnnotation,
SelfAnnotation,
ParentAnnotation,
AncestorAnnotation,
UnboundedAnnotation
} from './src/di/annotations';
export {Inject, InjectPromise, InjectLazy, Optional, Injectable} from './src/di/decorators';
export {
Inject,
Optional,
Injectable,
Visibility,
Self,
Parent,
Ancestor,
Unbounded
} from './src/di/decorators';
export {self} from './src/di/annotations_impl';
export {forwardRef, resolveForwardRef, ForwardRefFn} from './src/di/forward_ref';
export {resolveBindings, Injector} from './src/di/injector';
export {
resolveBindings,
Injector,
ProtoInjector,
PUBLIC_AND_PRIVATE,
PUBLIC,
PRIVATE,
undefinedValue,
InjectorInlineStrategy,
InjectorDynamicStrategy
} from './src/di/injector';
export {Binding, BindingBuilder, ResolvedBinding, Dependency, bind} from './src/di/binding';
export {Key, KeyRegistry, TypeLiteral} from './src/di/key';
export {
@ -26,6 +49,7 @@ export {
CyclicDependencyError,
InstantiationError,
InvalidBindingError,
NoAnnotationError
NoAnnotationError,
OutOfBoundsError
} from './src/di/exceptions';
export {OpaqueToken} from './src/di/opaque_token';

View File

@ -1,6 +1,5 @@
library angular2.core.decorators;
export '../annotations_impl/annotations.dart';
export '../annotations_impl/visibility.dart';
export '../annotations_impl/view.dart';
export '../annotations_impl/di.dart';

View File

@ -5,12 +5,6 @@ import {
DirectiveArgs
} from './annotations';
import {ViewAnnotation, ViewArgs} from './view';
import {
SelfAnnotation,
ParentAnnotation,
AncestorAnnotation,
UnboundedAnnotation
} from './visibility';
import {AttributeAnnotation, QueryAnnotation} from './di';
import {makeDecorator, makeParamDecorator, TypeDecorator, Class} from '../../util/decorators';
import {Type} from 'angular2/src/facade/lang';
@ -46,12 +40,6 @@ export var Directive = <Directive>makeDecorator(DirectiveAnnotation);
/* from view */
export var View = <View>makeDecorator(ViewAnnotation, (fn: any) => fn.View = View);
/* from visibility */
export var Self = makeParamDecorator(SelfAnnotation);
export var Parent = makeParamDecorator(ParentAnnotation);
export var Ancestor = makeParamDecorator(AncestorAnnotation);
export var Unbounded = makeParamDecorator(UnboundedAnnotation);
/* from di */
export var Attribute = makeParamDecorator(AttributeAnnotation);
export var Query = makeParamDecorator(QueryAnnotation);

View File

@ -1,3 +0,0 @@
library angular2.core.annotations.visibility;
export "../annotations_impl/visibility.dart";

View File

@ -1,6 +0,0 @@
export {
Self as SelfAnnotation,
Ancestor as AncestorAnnotation,
Parent as ParentAnnotation,
Unbounded as UnboundedAnnotation
} from '../annotations_impl/visibility';

View File

@ -1,6 +1,6 @@
import {CONST, CONST_EXPR} from 'angular2/src/facade/lang';
import {List} from 'angular2/src/facade/collection';
import {Injectable} from 'angular2/src/di/annotations_impl';
import {Injectable, self} from 'angular2/src/di/annotations_impl';
import {DEFAULT} from 'angular2/change_detection';
// type StringMap = {[idx: string]: string};
@ -788,7 +788,7 @@ export class Directive extends Injectable {
selector, properties, events, host, lifecycle, hostInjector, exportAs,
compileChildren = true,
}: DirectiveArgs = {}) {
super();
super(self);
this.selector = selector;
this.properties = properties;
this.events = events;

View File

@ -1,213 +0,0 @@
import {CONST, CONST_EXPR, isBlank} from 'angular2/src/facade/lang';
import {DependencyAnnotation} from 'angular2/src/di/annotations_impl';
@CONST()
export class Visibility extends DependencyAnnotation {
constructor(public depth: number, public crossComponentBoundaries: boolean,
public _includeSelf: boolean) {
super();
}
get includeSelf(): boolean { return isBlank(this._includeSelf) ? false : this._includeSelf; }
toString() {
return `@Visibility(depth: ${this.depth}, crossComponentBoundaries: ${this.crossComponentBoundaries}, includeSelf: ${this.includeSelf}})`;
}
}
/**
* Specifies that an injector should retrieve a dependency from its element.
*
* ## Example
*
* Here is a simple directive that retrieves a dependency from its element.
*
* ```
* @Directive({
* selector: '[dependency]',
* properties: [
* 'id: dependency'
* ]
* })
* class Dependency {
* id:string;
* }
*
*
* @Directive({
* selector: '[my-directive]'
* })
* class Dependency {
* constructor(@Self() dependency:Dependency) {
* expect(dependency.id).toEqual(1);
* };
* }
* ```
*
* We use this with the following HTML template:
*
* ```
*<div dependency="1" my-directive></div>
* ```
*
* @exportedAs angular2/annotations
*/
@CONST()
export class Self extends Visibility {
constructor() { super(0, false, true); }
toString() { return `@Self()`; }
}
// make constants after switching to ts2dart
export var self = new Self();
/**
* Specifies that an injector should retrieve a dependency from the direct parent.
*
* ## Example
*
* Here is a simple directive that retrieves a dependency from its parent element.
*
* ```
* @Directive({
* selector: '[dependency]',
* properties: [
* 'id: dependency'
* ]
* })
* class Dependency {
* id:string;
* }
*
*
* @Directive({
* selector: '[my-directive]'
* })
* class Dependency {
* constructor(@Parent() dependency:Dependency) {
* expect(dependency.id).toEqual(1);
* };
* }
* ```
*
* We use this with the following HTML template:
*
* ```
* <div dependency="1">
* <div dependency="2" my-directive></div>
* </div>
* ```
* The `@Parent()` annotation in our constructor forces the injector to retrieve the dependency from
* the
* parent element (even thought the current element could resolve it): Angular injects
* `dependency=1`.
*
* @exportedAs angular2/annotations
*/
@CONST()
export class Parent extends Visibility {
constructor({self}: {self?: boolean} = {}) { super(1, false, self); }
toString() { return `@Parent(self: ${this.includeSelf}})`; }
}
/**
* Specifies that an injector should retrieve a dependency from any ancestor element within the same
* shadow boundary.
*
* An ancestor is any element between the parent element and the shadow root.
*
* Use {@link Unbounded} if you need to cross upper shadow boundaries.
*
* ## Example
*
* Here is a simple directive that retrieves a dependency from an ancestor element.
*
* ```
* @Directive({
* selector: '[dependency]',
* properties: [
* 'id: dependency'
* ]
* })
* class Dependency {
* id:string;
* }
*
*
* @Directive({
* selector: '[my-directive]'
* })
* class Dependency {
* constructor(@Ancestor() dependency:Dependency) {
* expect(dependency.id).toEqual(2);
* };
* }
* ```
*
* We use this with the following HTML template:
*
* ```
* <div dependency="1">
* <div dependency="2">
* <div>
* <div dependency="3" my-directive></div>
* </div>
* </div>
* </div>
* ```
*
* The `@Ancestor()` annotation in our constructor forces the injector to retrieve the dependency
* from the
* nearest ancestor element:
* - The current element `dependency="3"` is skipped because it is not an ancestor.
* - Next parent has no directives `<div>`
* - Next parent has the `Dependency` directive and so the dependency is satisfied.
*
* Angular injects `dependency=2`.
*
* @exportedAs angular2/annotations
*/
@CONST()
export class Ancestor extends Visibility {
constructor({self}: {self?: boolean} = {}) { super(999999, false, self); }
toString() { return `@Ancestor(self: ${this.includeSelf}})`; }
}
/**
* Specifies that an injector should retrieve a dependency from any ancestor element, crossing
* component boundaries.
*
* Use {@link Ancestor} to look for ancestors within the current shadow boundary only.
*
* ## Example
*
* Here is a simple directive that retrieves a dependency from an ancestor element.
*
* ```
* @Directive({
* selector: '[dependency]',
* properties: [
* 'id: dependency'
* ]
* })
* class Dependency {
* id:string;
* }
*
*
* @Directive({
* selector: '[my-directive]'
* })
* class Dependency {
* constructor(@Unbounded() dependency:Dependency) {
* expect(dependency.id).toEqual(2);
* };
* }
* ```
*
* @exportedAs angular2/annotations
*/
@CONST()
export class Unbounded extends Visibility {
constructor({self}: {self?: boolean} = {}) { super(999999, true, self); }
toString() { return `@Unbounded(self: ${this.includeSelf}})`; }
}

View File

@ -79,20 +79,19 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
.toValue(DOM.defaultDoc()),
bind(appComponentTypeToken).toValue(appComponentType),
bind(appComponentRefToken)
.toAsyncFactory(
.toFactory(
(dynamicComponentLoader, injector, testability, registry) => {
// TODO(rado): investigate whether to support bindings on root component.
return dynamicComponentLoader.loadAsRoot(appComponentType, null, injector)
.then((componentRef) => {
registry.registerApplication(componentRef.location.nativeElement, testability);
return componentRef;
});
},
[DynamicComponentLoader, Injector, Testability, TestabilityRegistry]),
bind(appComponentType).toFactory((ref) => ref.instance, [appComponentRefToken]),
bind(appComponentType)
.toFactory((p: Promise<any>) => p.then(ref => ref.instance), [appComponentRefToken]),
bind(LifeCycle)
.toFactory((exceptionHandler) => new LifeCycle(exceptionHandler, null, assertionsEnabled()),
[ExceptionHandler]),
@ -293,10 +292,9 @@ export function bootstrap(appComponentType: Type,
// index.html and main.js are possible.
var appInjector = _createAppInjector(appComponentType, componentInjectableBindings, zone);
PromiseWrapper.then(
appInjector.asyncGet(appComponentRefToken),
(componentRef) => {
var compRefToken: Promise<any> =
PromiseWrapper.wrap(() => appInjector.get(appComponentRefToken));
var tick = (componentRef) => {
var appChangeDetector = internalView(componentRef.hostView).changeDetector;
// retrieve life cycle: may have already been created if injected in root component
var lc = appInjector.get(LifeCycle);
@ -304,8 +302,8 @@ export function bootstrap(appComponentType: Type,
lc.tick(); // the first tick that will bootstrap the app
bootstrapProcess.resolve(new ApplicationRef(componentRef, appComponentType, appInjector));
},
};
PromiseWrapper.then(compRefToken, tick,
(err, stackTrace) => {bootstrapProcess.reject(err, stackTrace)});
});

File diff suppressed because it is too large Load Diff

View File

@ -5,9 +5,12 @@
export {
Inject as InjectAnnotation,
InjectPromise as InjectPromiseAnnotation,
InjectLazy as InjectLazyAnnotation,
Optional as OptionalAnnotation,
Injectable as InjectableAnnotation,
Visibility as VisibilityAnnotation,
Self as SelfAnnotation,
Parent as ParentAnnotation,
Ancestor as AncestorAnnotation,
Unbounded as UnboundedAnnotation,
DependencyAnnotation, // abstract base class, does not need a decorator
} from './annotations_impl';

View File

@ -1,4 +1,4 @@
import {CONST, stringify} from "angular2/src/facade/lang";
import {CONST, CONST_EXPR, stringify, isBlank, isPresent} from "angular2/src/facade/lang";
/**
* A parameter annotation that specifies a dependency.
@ -18,44 +18,6 @@ export class Inject {
toString(): string { return `@Inject(${stringify(this.token)})`; }
}
/**
* A parameter annotation that specifies a `Promise` of a dependency.
*
* ```
* class AComponent {
* constructor(@InjectPromise(MyService) aServicePromise:Promise<MyService>) {
* aServicePromise.then(aService:MyService => ...);
* }
* }
* ```
*
* @exportedAs angular2/di_annotations
*/
@CONST()
export class InjectPromise {
constructor(public token) {}
toString(): string { return `@InjectPromise(${stringify(this.token)})`; }
}
/**
* A parameter annotation that creates a synchronous lazy dependency.
*
* ```
* class AComponent {
* constructor(@InjectLazy(MyService) aServiceFn:Function) {
* var aService:MyService = aServiceFn();
* }
* }
* ```
*
* @exportedAs angular2/di_annotations
*/
@CONST()
export class InjectLazy {
constructor(public token) {}
toString(): string { return `@InjectLazy(${stringify(this.token)})`; }
}
/**
* A parameter annotation that marks a dependency as optional. {@link Injector} provides `null` if
* the dependency is not found.
@ -124,4 +86,172 @@ export class DependencyAnnotation {
*/
@CONST()
export class Injectable {
constructor(public visibility: Visibility = unbounded) {}
}
/**
* Specifies how injector should resolve a dependency.
*
* See {@link Self}, {@link Parent}, {@link Ancestor}, {@link Unbounded}.
*
* @exportedAs angular2/di_annotations
*/
@CONST()
export class Visibility extends DependencyAnnotation {
constructor(public depth: number, public crossComponentBoundaries: boolean,
public _includeSelf: boolean) {
super();
}
get includeSelf(): boolean { return isBlank(this._includeSelf) ? false : this._includeSelf; }
toString(): string {
return `@Visibility(depth: ${this.depth}, crossComponentBoundaries: ${this.crossComponentBoundaries}, includeSelf: ${this.includeSelf}})`;
}
}
/**
* Specifies that an injector should retrieve a dependency from itself.
*
* ## Example
*
* ```
* class Dependency {
* }
*
* class NeedsDependency {
* constructor(public @Self() dependency:Dependency) {}
* }
*
* var inj = Injector.resolveAndCreate([Dependency, NeedsDependency]);
* var nd = inj.get(NeedsDependency);
* expect(nd.dependency).toBeAnInstanceOf(Dependency);
* ```
*
* @exportedAs angular2/di
*/
@CONST()
export class Self extends Visibility {
constructor() { super(0, false, true); }
toString(): string { return `@Self()`; }
}
export const self = CONST_EXPR(new Self());
/**
* Specifies that an injector should retrieve a dependency from the direct parent.
*
* ## Example
*
* ```
* class Dependency {
* }
*
* class NeedsDependency {
* constructor(public @Parent() dependency:Dependency) {}
* }
*
* var parent = Injector.resolveAndCreate([
* bind(Dependency).toClass(ParentDependency)
* ]);
* var child = parent.resolveAndCreateChild([NeedsDependency, Depedency]);
* var nd = child.get(NeedsDependency);
* expect(nd.dependency).toBeAnInstanceOf(ParentDependency);
* ```
*
* You can make an injector to retrive a dependency either from itself or its direct parent by
* setting self to true.
*
* ```
* class NeedsDependency {
* constructor(public @Parent({self:true}) dependency:Dependency) {}
* }
* ```
*
* @exportedAs angular2/di
*/
@CONST()
export class Parent extends Visibility {
constructor({self}: {self?: boolean} = {}) { super(1, false, self); }
toString(): string { return `@Parent(self: ${this.includeSelf}})`; }
}
/**
* Specifies that an injector should retrieve a dependency from any ancestor from the same boundary.
*
* ## Example
*
* ```
* class Dependency {
* }
*
* class NeedsDependency {
* constructor(public @Ancestor() dependency:Dependency) {}
* }
*
* var parent = Injector.resolveAndCreate([
* bind(Dependency).toClass(AncestorDependency)
* ]);
* var child = parent.resolveAndCreateChild([]);
* var grandChild = child.resolveAndCreateChild([NeedsDependency, Depedency]);
* var nd = grandChild.get(NeedsDependency);
* expect(nd.dependency).toBeAnInstanceOf(AncestorDependency);
* ```
*
* You can make an injector to retrive a dependency either from itself or its ancestor by setting
* self to true.
*
* ```
* class NeedsDependency {
* constructor(public @Ancestor({self:true}) dependency:Dependency) {}
* }
* ```
*
* @exportedAs angular2/di
*/
@CONST()
export class Ancestor extends Visibility {
constructor({self}: {self?: boolean} = {}) { super(999999, false, self); }
toString(): string { return `@Ancestor(self: ${this.includeSelf}})`; }
}
/**
* Specifies that an injector should retrieve a dependency from any ancestor, crossing boundaries.
*
* ## Example
*
* ```
* class Dependency {
* }
*
* class NeedsDependency {
* constructor(public @Ancestor() dependency:Dependency) {}
* }
*
* var parent = Injector.resolveAndCreate([
* bind(Dependency).toClass(AncestorDependency)
* ]);
* var child = parent.resolveAndCreateChild([]);
* var grandChild = child.resolveAndCreateChild([NeedsDependency, Depedency]);
* var nd = grandChild.get(NeedsDependency);
* expect(nd.dependency).toBeAnInstanceOf(AncestorDependency);
* ```
*
* You can make an injector to retrive a dependency either from itself or its ancestor by setting
* self to true.
*
* ```
* class NeedsDependency {
* constructor(public @Ancestor({self:true}) dependency:Dependency) {}
* }
* ```
*
* @exportedAs angular2/di
*/
@CONST()
export class Unbounded extends Visibility {
constructor({self}: {self?: boolean} = {}) { super(999999, true, self); }
toString(): string { return `@Unbounded(self: ${this.includeSelf}})`; }
}
export const unbounded = CONST_EXPR(new Unbounded({self: true}));

View File

@ -13,9 +13,10 @@ import {reflector} from 'angular2/src/reflection/reflection';
import {Key} from './key';
import {
Inject,
InjectLazy,
InjectPromise,
Injectable,
Visibility,
Optional,
unbounded,
DependencyAnnotation
} from './annotations_impl';
import {NoAnnotationError} from './exceptions';
@ -25,10 +26,10 @@ import {resolveForwardRef} from './forward_ref';
* @private
*/
export class Dependency {
constructor(public key: Key, public asPromise: boolean, public lazy: boolean,
public optional: boolean, public properties: List<any>) {}
constructor(public key: Key, public optional: boolean, public visibility: Visibility,
public properties: List<any>) {}
static fromKey(key: Key) { return new Dependency(key, false, false, false, []); }
static fromKey(key: Key) { return new Dependency(key, false, _defaulVisiblity(key.token), []); }
}
const _EMPTY_LIST = CONST_EXPR([]);
@ -158,35 +159,7 @@ export class Binding {
toFactory: Function;
/**
* Binds 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: [Number]})
* ]);
*
* 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 though `Number` has an async factory, the `String`
* factory function takes the resolved value. This shows that the {@link Injector} delays
*executing the
*`String` factory
* until after the `Number` is resolved. This can only be done if the `token` is retrieved using
* the `asyncGet` API in the {@link Injector}.
*
*/
toAsyncFactory: Function;
/**
* Used in conjunction with `toFactory` or `toAsyncFactory` and specifies a set of dependencies
* Used in conjunction with `toFactory` and specifies a set of dependencies
* (as `token`s) which should be injected into the factory function.
*
* ## Example
@ -204,12 +177,11 @@ export class Binding {
*/
dependencies: List<any>;
constructor(token, {toClass, toValue, toAlias, toFactory, toAsyncFactory, deps}: {
constructor(token, {toClass, toValue, toAlias, toFactory, deps}: {
toClass?: Type,
toValue?: any,
toAlias?: any,
toFactory?: Function,
toAsyncFactory?: Function,
deps?: List<any>
}) {
this.token = token;
@ -217,7 +189,6 @@ export class Binding {
this.toValue = toValue;
this.toAlias = toAlias;
this.toFactory = toFactory;
this.toAsyncFactory = toAsyncFactory;
this.dependencies = deps;
}
@ -230,7 +201,6 @@ export class Binding {
resolve(): ResolvedBinding {
var factoryFn: Function;
var resolvedDeps;
var isAsync = false;
if (isPresent(this.toClass)) {
var toClass = resolveForwardRef(this.toClass);
factoryFn = reflector.factory(toClass);
@ -241,16 +211,12 @@ export class Binding {
} else if (isPresent(this.toFactory)) {
factoryFn = this.toFactory;
resolvedDeps = _constructDependencies(this.toFactory, this.dependencies);
} else if (isPresent(this.toAsyncFactory)) {
factoryFn = this.toAsyncFactory;
resolvedDeps = _constructDependencies(this.toAsyncFactory, this.dependencies);
isAsync = true;
} else {
factoryFn = () => this.toValue;
resolvedDeps = _EMPTY_LIST;
}
return new ResolvedBinding(Key.get(this.token), factoryFn, resolvedDeps, isAsync);
return new ResolvedBinding(Key.get(this.token), factoryFn, resolvedDeps);
}
}
@ -278,12 +244,7 @@ export class ResolvedBinding {
/**
* Arguments (dependencies) to the `factory` function.
*/
public dependencies: List<Dependency>,
/**
* Specifies whether the `factory` function returns a `Promise`.
*/
public providedAsPromise: boolean) {}
public dependencies: List<Dependency>) {}
}
/**
@ -417,33 +378,6 @@ export class BindingBuilder {
toFactory(factoryFunction: Function, dependencies?: List<any>): Binding {
return new Binding(this.token, {toFactory: factoryFunction, deps: dependencies});
}
/**
* Binds 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; }, [Number])
* ]);
*
* 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 though `Number` has an async factory, the `String`
* factory function takes the resolved value. This shows that the {@link Injector} delays
* executing of the `String` factory
* until after the `Number` is resolved. This can only be done if the `token` is retrieved using
* the `asyncGet` API in the {@link Injector}.
*/
toAsyncFactory(factoryFunction: Function, dependencies?: List<any>): Binding {
return new Binding(this.token, {toAsyncFactory: factoryFunction, deps: dependencies});
}
}
function _constructDependencies(factoryFunction: Function,
@ -470,33 +404,30 @@ function _extractToken(typeOrFunc, annotations /*List<any> | any*/,
var depProps = [];
var token = null;
var optional = false;
var lazy = false;
var asPromise = false;
if (!isArray(annotations)) {
return _createDependency(annotations, asPromise, lazy, optional, depProps);
return _createDependency(annotations, optional, _defaulVisiblity(annotations), depProps);
}
var visibility = null;
var defaultVisibility = unbounded;
for (var i = 0; i < annotations.length; ++i) {
var paramAnnotation = annotations[i];
if (paramAnnotation instanceof Type) {
token = paramAnnotation;
defaultVisibility = _defaulVisiblity(token);
} else if (paramAnnotation instanceof Inject) {
token = paramAnnotation.token;
} else if (paramAnnotation instanceof InjectPromise) {
token = paramAnnotation.token;
asPromise = true;
} else if (paramAnnotation instanceof InjectLazy) {
token = paramAnnotation.token;
lazy = true;
} else if (paramAnnotation instanceof Optional) {
optional = true;
} else if (paramAnnotation instanceof Visibility) {
visibility = paramAnnotation;
} else if (paramAnnotation instanceof DependencyAnnotation) {
if (isPresent(paramAnnotation.token)) {
token = paramAnnotation.token;
@ -505,15 +436,29 @@ function _extractToken(typeOrFunc, annotations /*List<any> | any*/,
}
}
if (isBlank(visibility)) {
visibility = defaultVisibility;
}
token = resolveForwardRef(token);
if (isPresent(token)) {
return _createDependency(token, asPromise, lazy, optional, depProps);
return _createDependency(token, optional, visibility, depProps);
} else {
throw new NoAnnotationError(typeOrFunc, params);
}
}
function _createDependency(token, asPromise, lazy, optional, depProps): Dependency {
return new Dependency(Key.get(token), asPromise, lazy, optional, depProps);
function _defaulVisiblity(typeOrFunc) {
try {
if (!(typeOrFunc instanceof Type)) return unbounded;
var f = ListWrapper.filter(reflector.annotations(typeOrFunc), s => s instanceof Injectable);
return f.length === 0 ? unbounded : f[0].visibility;
} catch (e) {
return unbounded;
}
}
function _createDependency(token, optional, visibility, depProps): Dependency {
return new Dependency(Key.get(token), optional, visibility, depProps);
}

View File

@ -1,14 +1,20 @@
import {
InjectAnnotation,
InjectPromiseAnnotation,
InjectLazyAnnotation,
OptionalAnnotation,
InjectableAnnotation
InjectableAnnotation,
VisibilityAnnotation,
SelfAnnotation,
ParentAnnotation,
AncestorAnnotation,
UnboundedAnnotation
} from './annotations';
import {makeDecorator, makeParamDecorator} from '../util/decorators';
export var Inject = makeParamDecorator(InjectAnnotation);
export var InjectPromise = makeParamDecorator(InjectPromiseAnnotation);
export var InjectLazy = makeParamDecorator(InjectLazyAnnotation);
export var Optional = makeParamDecorator(OptionalAnnotation);
export var Injectable = makeDecorator(InjectableAnnotation);
export var Visibility = makeParamDecorator(VisibilityAnnotation);
export var Self = makeParamDecorator(SelfAnnotation);
export var Parent = makeParamDecorator(ParentAnnotation);
export var Ancestor = makeParamDecorator(AncestorAnnotation);
export var Unbounded = makeParamDecorator(UnboundedAnnotation);

View File

@ -140,15 +140,17 @@ export class CyclicDependencyError extends AbstractBindingError {
export class InstantiationError extends AbstractBindingError {
cause;
causeKey;
stack;
// TODO(tbosch): Can't do key:Key as this results in a circular dependency!
constructor(cause, key) {
constructor(cause, stack, key) {
super(key, function(keys: List<any>) {
var first = stringify(ListWrapper.first(keys).token);
return `Error during instantiation of ${first}!${constructResolvingPath(keys)}. ORIGINAL ERROR: ${cause}`;
});
this.cause = cause;
this.causeKey = key;
this.stack = stack;
}
}
@ -198,3 +200,18 @@ export class NoAnnotationError extends BaseException {
toString(): string { return this.message; }
}
/**
* Thrown when getting an object by index.
*
* @exportedAs angular2/di_errors
*/
export class OutOfBoundsError extends BaseException {
message: string;
constructor(index) {
super();
this.message = `Index ${index} is out-of-bounds.`;
}
toString(): string { return this.message; }
}

View File

@ -8,22 +8,371 @@ import {
AsyncBindingError,
CyclicDependencyError,
InstantiationError,
InvalidBindingError
InvalidBindingError,
OutOfBoundsError
} from './exceptions';
import {FunctionWrapper, Type, isPresent, isBlank, CONST_EXPR} from 'angular2/src/facade/lang';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {Key} from './key';
import {resolveForwardRef} from './forward_ref';
import {self, unbounded} from './annotations_impl';
const _constructing = CONST_EXPR(new Object());
const _notFound = CONST_EXPR(new Object());
class _Waiting {
constructor(public promise: Promise<any>) {}
// Threshold for the dynamic version
const _MAX_CONSTRUCTION_COUNTER = 10;
export const undefinedValue = CONST_EXPR(new Object());
export const PUBLIC = 1;
export const PRIVATE = 2;
export const PUBLIC_AND_PRIVATE = 3;
export interface ProtoInjectorStrategy {
getBindingAtIndex(index: number): ResolvedBinding;
createInjectorStrategy(inj: Injector): InjectorStrategy;
}
function _isWaiting(obj): boolean {
return obj instanceof _Waiting;
export class ProtoInjectorInlineStrategy implements ProtoInjectorStrategy {
binding0: ResolvedBinding = null;
binding1: ResolvedBinding = null;
binding2: ResolvedBinding = null;
binding3: ResolvedBinding = null;
binding4: ResolvedBinding = null;
binding5: ResolvedBinding = null;
binding6: ResolvedBinding = null;
binding7: ResolvedBinding = null;
binding8: ResolvedBinding = null;
binding9: ResolvedBinding = null;
keyId0: number = null;
keyId1: number = null;
keyId2: number = null;
keyId3: number = null;
keyId4: number = null;
keyId5: number = null;
keyId6: number = null;
keyId7: number = null;
keyId8: number = null;
keyId9: number = null;
visibility0: number = null;
visibility1: number = null;
visibility2: number = null;
visibility3: number = null;
visibility4: number = null;
visibility5: number = null;
visibility6: number = null;
visibility7: number = null;
visibility8: number = null;
visibility9: number = null;
constructor(protoEI: ProtoInjector, bd: any[]) {
var length = bd.length;
if (length > 0) {
this.binding0 = bd[0].binding;
this.keyId0 = bd[0].getKeyId();
this.visibility0 = bd[0].visibility;
}
if (length > 1) {
this.binding1 = bd[1].binding;
this.keyId1 = bd[1].getKeyId();
this.visibility1 = bd[1].visibility;
}
if (length > 2) {
this.binding2 = bd[2].binding;
this.keyId2 = bd[2].getKeyId();
this.visibility2 = bd[2].visibility;
}
if (length > 3) {
this.binding3 = bd[3].binding;
this.keyId3 = bd[3].getKeyId();
this.visibility3 = bd[3].visibility;
}
if (length > 4) {
this.binding4 = bd[4].binding;
this.keyId4 = bd[4].getKeyId();
this.visibility4 = bd[4].visibility;
}
if (length > 5) {
this.binding5 = bd[5].binding;
this.keyId5 = bd[5].getKeyId();
this.visibility5 = bd[5].visibility;
}
if (length > 6) {
this.binding6 = bd[6].binding;
this.keyId6 = bd[6].getKeyId();
this.visibility6 = bd[6].visibility;
}
if (length > 7) {
this.binding7 = bd[7].binding;
this.keyId7 = bd[7].getKeyId();
this.visibility7 = bd[7].visibility;
}
if (length > 8) {
this.binding8 = bd[8].binding;
this.keyId8 = bd[8].getKeyId();
this.visibility8 = bd[8].visibility;
}
if (length > 9) {
this.binding9 = bd[9].binding;
this.keyId9 = bd[9].getKeyId();
this.visibility9 = bd[9].visibility;
}
}
getBindingAtIndex(index: number): any {
if (index == 0) return this.binding0;
if (index == 1) return this.binding1;
if (index == 2) return this.binding2;
if (index == 3) return this.binding3;
if (index == 4) return this.binding4;
if (index == 5) return this.binding5;
if (index == 6) return this.binding6;
if (index == 7) return this.binding7;
if (index == 8) return this.binding8;
if (index == 9) return this.binding9;
throw new OutOfBoundsError(index);
}
createInjectorStrategy(injector: Injector): InjectorStrategy {
return new InjectorInlineStrategy(injector, this);
}
}
export class ProtoInjectorDynamicStrategy implements ProtoInjectorStrategy {
bindings: ResolvedBinding[];
keyIds: number[];
visibilities: number[];
constructor(protoInj: ProtoInjector, bd: any[]) {
var len = bd.length;
this.bindings = ListWrapper.createFixedSize(len);
this.keyIds = ListWrapper.createFixedSize(len);
this.visibilities = ListWrapper.createFixedSize(len);
for (var i = 0; i < len; i++) {
this.bindings[i] = bd[i].binding;
this.keyIds[i] = bd[i].getKeyId();
this.visibilities[i] = bd[i].visibility;
}
}
getBindingAtIndex(index: number): any {
if (index < 0 || index >= this.bindings.length) {
throw new OutOfBoundsError(index);
}
return this.bindings[index];
}
createInjectorStrategy(ei: Injector): InjectorStrategy {
return new InjectorDynamicStrategy(this, ei);
}
}
export class ProtoInjector {
_strategy: ProtoInjectorStrategy;
constructor(public parent: ProtoInjector, rb: any[], public distanceToParent: number) {
this._strategy = rb.length > _MAX_CONSTRUCTION_COUNTER ?
new ProtoInjectorDynamicStrategy(this, rb) :
new ProtoInjectorInlineStrategy(this, rb);
}
getBindingAtIndex(index: number): any { return this._strategy.getBindingAtIndex(index); }
}
export interface InjectorStrategy {
getObjByKeyId(keyId: number, visibility: number): any;
getObjAtIndex(index: number): any;
getMaxNumberOfObjects(): number;
hydrate(): void;
dehydrate(): void;
}
export class InjectorInlineStrategy implements InjectorStrategy {
obj0: any = null;
obj1: any = null;
obj2: any = null;
obj3: any = null;
obj4: any = null;
obj5: any = null;
obj6: any = null;
obj7: any = null;
obj8: any = null;
obj9: any = null;
constructor(public injector: Injector, public protoStrategy: ProtoInjectorInlineStrategy) {}
hydrate(): void {
var p = this.protoStrategy;
var inj = this.injector;
if (isPresent(p.keyId0) && isBlank(this.obj0)) this.obj0 = inj._new(p.binding0);
if (isPresent(p.keyId1) && isBlank(this.obj1)) this.obj1 = inj._new(p.binding1);
if (isPresent(p.keyId2) && isBlank(this.obj2)) this.obj2 = inj._new(p.binding2);
if (isPresent(p.keyId3) && isBlank(this.obj3)) this.obj3 = inj._new(p.binding3);
if (isPresent(p.keyId4) && isBlank(this.obj4)) this.obj4 = inj._new(p.binding4);
if (isPresent(p.keyId5) && isBlank(this.obj5)) this.obj5 = inj._new(p.binding5);
if (isPresent(p.keyId6) && isBlank(this.obj6)) this.obj6 = inj._new(p.binding6);
if (isPresent(p.keyId7) && isBlank(this.obj7)) this.obj7 = inj._new(p.binding7);
if (isPresent(p.keyId8) && isBlank(this.obj8)) this.obj8 = inj._new(p.binding8);
if (isPresent(p.keyId9) && isBlank(this.obj9)) this.obj9 = inj._new(p.binding9);
}
dehydrate() {
this.obj0 = null;
this.obj1 = null;
this.obj2 = null;
this.obj3 = null;
this.obj4 = null;
this.obj5 = null;
this.obj6 = null;
this.obj7 = null;
this.obj8 = null;
this.obj9 = null;
}
getObjByKeyId(keyId: number, visibility: number): any {
var p = this.protoStrategy;
var inj = this.injector;
if (p.keyId0 === keyId && (p.visibility0 & visibility) > 0) {
if (isBlank(this.obj0)) {
this.obj0 = inj._new(p.binding0);
}
return this.obj0;
}
if (p.keyId1 === keyId && (p.visibility1 & visibility) > 0) {
if (isBlank(this.obj1)) {
this.obj1 = inj._new(p.binding1);
}
return this.obj1;
}
if (p.keyId2 === keyId && (p.visibility2 & visibility) > 0) {
if (isBlank(this.obj2)) {
this.obj2 = inj._new(p.binding2);
}
return this.obj2;
}
if (p.keyId3 === keyId && (p.visibility3 & visibility) > 0) {
if (isBlank(this.obj3)) {
this.obj3 = inj._new(p.binding3);
}
return this.obj3;
}
if (p.keyId4 === keyId && (p.visibility4 & visibility) > 0) {
if (isBlank(this.obj4)) {
this.obj4 = inj._new(p.binding4);
}
return this.obj4;
}
if (p.keyId5 === keyId && (p.visibility5 & visibility) > 0) {
if (isBlank(this.obj5)) {
this.obj5 = inj._new(p.binding5);
}
return this.obj5;
}
if (p.keyId6 === keyId && (p.visibility6 & visibility) > 0) {
if (isBlank(this.obj6)) {
this.obj6 = inj._new(p.binding6);
}
return this.obj6;
}
if (p.keyId7 === keyId && (p.visibility7 & visibility) > 0) {
if (isBlank(this.obj7)) {
this.obj7 = inj._new(p.binding7);
}
return this.obj7;
}
if (p.keyId8 === keyId && (p.visibility8 & visibility) > 0) {
if (isBlank(this.obj8)) {
this.obj8 = inj._new(p.binding8);
}
return this.obj8;
}
if (p.keyId9 === keyId && (p.visibility9 & visibility) > 0) {
if (isBlank(this.obj9)) {
this.obj9 = inj._new(p.binding9);
}
return this.obj9;
}
return undefinedValue;
}
getObjAtIndex(index: number): any {
if (index == 0) return this.obj0;
if (index == 1) return this.obj1;
if (index == 2) return this.obj2;
if (index == 3) return this.obj3;
if (index == 4) return this.obj4;
if (index == 5) return this.obj5;
if (index == 6) return this.obj6;
if (index == 7) return this.obj7;
if (index == 8) return this.obj8;
if (index == 9) return this.obj9;
throw new OutOfBoundsError(index);
}
getMaxNumberOfObjects(): number { return _MAX_CONSTRUCTION_COUNTER; }
}
export class InjectorDynamicStrategy implements InjectorStrategy {
objs: any[];
constructor(public protoStrategy: ProtoInjectorDynamicStrategy, public injector: Injector) {
this.objs = ListWrapper.createFixedSize(protoStrategy.bindings.length);
}
hydrate(): void {
var p = this.protoStrategy;
for (var i = 0; i < p.keyIds.length; i++) {
if (isPresent(p.keyIds[i]) && isBlank(this.objs[i])) {
this.objs[i] = this.injector._new(p.bindings[i]);
}
}
}
dehydrate(): void { ListWrapper.fill(this.objs, null); }
getObjByKeyId(keyId: number, visibility: number): any {
var p = this.protoStrategy;
for (var i = 0; i < p.keyIds.length; i++) {
if (p.keyIds[i] === keyId && (p.visibilities[i] & visibility) > 0) {
if (isBlank(this.objs[i])) {
this.objs[i] = this.injector._new(p.bindings[i]);
}
return this.objs[i];
}
}
return undefinedValue;
}
getObjAtIndex(index: number): any {
if (index < 0 || index >= this.objs.length) {
throw new OutOfBoundsError(index);
}
return this.objs[index];
}
getMaxNumberOfObjects(): number { return this.objs.length; }
}
export class BindingData {
constructor(public binding: ResolvedBinding, public visibility: number){};
getKeyId(): number { return this.binding.key.id; }
}
/**
@ -67,10 +416,6 @@ function _isWaiting(obj): boolean {
* @exportedAs angular2/di
*/
export class Injector {
private _instances: List<any>;
private _asyncStrategy: _AsyncInjectorStrategy;
private _syncStrategy: _SyncInjectorStrategy;
/**
* Turns a list of binding definitions into an internal resolved list of resolved bindings.
*
@ -108,7 +453,11 @@ export class Injector {
*/
static resolveAndCreate(bindings: List<Type | Binding | List<any>>,
{defaultBindings = false}: any = {}): Injector {
return new Injector(Injector.resolve(bindings), null, defaultBindings);
var resolvedBindings = Injector.resolve(bindings);
var bd = resolvedBindings.map(b => new BindingData(b, PUBLIC));
var proto = new ProtoInjector(null, bd, 0);
var inj = new Injector(proto);
return inj;
}
/**
@ -121,53 +470,44 @@ export class Injector {
*/
static fromResolvedBindings(bindings: List<ResolvedBinding>,
{defaultBindings = false}: any = {}): Injector {
return new Injector(bindings, null, defaultBindings);
var bd = bindings.map(b => new BindingData(b, PUBLIC));
var proto = new ProtoInjector(null, bd, 0);
var inj = new Injector(proto);
return inj;
}
/**
* @param `bindings` A sparse list of {@link ResolvedBinding}s. See `resolve` for the
* {@link Injector}.
* @param `parent` Parent Injector or `null` if root Injector.
* @param `defaultBindings` Setting to true will auto-create bindings. (Only use with root
* injector.)
*/
constructor(private _bindings: List<ResolvedBinding>, private _parent: Injector,
private _defaultBindings: boolean) {
this._instances = this._createInstances();
this._asyncStrategy = new _AsyncInjectorStrategy(this);
this._syncStrategy = new _SyncInjectorStrategy(this);
_strategy: InjectorStrategy;
_parent: Injector;
_host: Injector;
_constructionCounter: number = 0;
// TODO vsavkin remove it after DI and EI are merged
private _ei: any;
constructor(public _proto: ProtoInjector) {
this._strategy = _proto._strategy.createInjectorStrategy(this);
}
/**
* Direct parent of this injector.
*/
get(token): any { return this._getByKey(Key.get(token), unbounded, false, null); }
getOptional(token): any { return this._getByKey(Key.get(token), unbounded, true, null); }
getObjAtIndex(index: number): any { return this._strategy.getObjAtIndex(index); }
get parent(): Injector { return this._parent; }
/**
* Retrieves an instance from the injector.
*
* @param `token`: usually the `Type` of an object. (Same as the 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); }
get strategy() { return this._strategy; }
hydrate(parent: Injector, host: Injector, ei: any) {
this._constructionCounter = 0;
this._parent = parent;
this._host = host;
this._ei = ei;
/**
* Retrieves an instance from the injector.
*
* @param `token`: usually a `Type`. (Same as the 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); }
this._strategy.hydrate();
}
/**
* Retrieves an instance from the injector asynchronously. Used with asynchronous bindings.
*
* @param `token`: usually a `Type`. (Same as token used while setting up a binding).
* @returns a `Promise` which resolves to the instance represented by the token.
*/
asyncGet(token): Promise<any> { return this._getByKey(Key.get(token), true, false, false); }
dehydrate(): void { this._strategy.dehydrate(); }
/**
* Creates a child injector and loads a new set of bindings into it.
@ -181,7 +521,12 @@ export class Injector {
*
*/
resolveAndCreateChild(bindings: List<Type | Binding | List<any>>): Injector {
return new Injector(Injector.resolve(bindings), this, false);
var resovledBindings = Injector.resolve(bindings);
var bd = resovledBindings.map(b => new BindingData(b, PUBLIC));
var proto = new ProtoInjector(this._proto, bd, 1);
var inj = new Injector(proto);
inj._parent = this;
return inj;
}
/**
@ -192,26 +537,184 @@ export class Injector {
* @returns a new child {@link Injector}.
*/
createChildFromResolved(bindings: List<ResolvedBinding>): Injector {
return new Injector(bindings, this, false);
var bd = bindings.map(b => new BindingData(b, PUBLIC));
var proto = new ProtoInjector(this._proto, bd, 1);
var inj = new Injector(proto);
inj._parent = this;
return inj;
}
_createInstances(): List<any> { return ListWrapper.createFixedSize(Key.numberOfKeys + 1); }
_getByKey(key: Key, returnPromise: boolean, returnLazy: boolean, optional: boolean) {
if (returnLazy) {
return () => this._getByKey(key, returnPromise, false, optional);
_new(binding: ResolvedBinding): any {
if (this._constructionCounter++ > this._strategy.getMaxNumberOfObjects()) {
throw new CyclicDependencyError(binding.key);
}
var strategy = returnPromise ? this._asyncStrategy : this._syncStrategy;
var factory = binding.factory;
var deps = binding.dependencies;
var length = deps.length;
var instance = strategy.readFromCache(key);
if (instance !== _notFound) return instance;
var d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17, d18, d19;
try {
d0 = length > 0 ? this._getByDependency(deps[0], binding.key) : null;
d1 = length > 1 ? this._getByDependency(deps[1], binding.key) : null;
d2 = length > 2 ? this._getByDependency(deps[2], binding.key) : null;
d3 = length > 3 ? this._getByDependency(deps[3], binding.key) : null;
d4 = length > 4 ? this._getByDependency(deps[4], binding.key) : null;
d5 = length > 5 ? this._getByDependency(deps[5], binding.key) : null;
d6 = length > 6 ? this._getByDependency(deps[6], binding.key) : null;
d7 = length > 7 ? this._getByDependency(deps[7], binding.key) : null;
d8 = length > 8 ? this._getByDependency(deps[8], binding.key) : null;
d9 = length > 9 ? this._getByDependency(deps[9], binding.key) : null;
d10 = length > 10 ? this._getByDependency(deps[10], binding.key) : null;
d11 = length > 11 ? this._getByDependency(deps[11], binding.key) : null;
d12 = length > 12 ? this._getByDependency(deps[12], binding.key) : null;
d13 = length > 13 ? this._getByDependency(deps[13], binding.key) : null;
d14 = length > 14 ? this._getByDependency(deps[14], binding.key) : null;
d15 = length > 15 ? this._getByDependency(deps[15], binding.key) : null;
d16 = length > 16 ? this._getByDependency(deps[16], binding.key) : null;
d17 = length > 17 ? this._getByDependency(deps[17], binding.key) : null;
d18 = length > 18 ? this._getByDependency(deps[18], binding.key) : null;
d19 = length > 19 ? this._getByDependency(deps[19], binding.key) : null;
} catch (e) {
if (e instanceof AbstractBindingError) e.addKey(binding.key);
throw e;
}
instance = strategy.instantiate(key);
if (instance !== _notFound) return instance;
var obj;
try {
switch (length) {
case 0:
obj = factory();
break;
case 1:
obj = factory(d0);
break;
case 2:
obj = factory(d0, d1);
break;
case 3:
obj = factory(d0, d1, d2);
break;
case 4:
obj = factory(d0, d1, d2, d3);
break;
case 5:
obj = factory(d0, d1, d2, d3, d4);
break;
case 6:
obj = factory(d0, d1, d2, d3, d4, d5);
break;
case 7:
obj = factory(d0, d1, d2, d3, d4, d5, d6);
break;
case 8:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7);
break;
case 9:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8);
break;
case 10:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9);
break;
case 11:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10);
break;
case 12:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11);
break;
case 13:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12);
break;
case 14:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13);
break;
case 15:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14);
break;
case 16:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15);
break;
case 17:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16);
break;
case 18:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16,
d17);
break;
case 19:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16,
d17, d18);
break;
case 20:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16,
d17, d18, d19);
break;
}
} catch (e) {
throw new InstantiationError(e, e.stack, binding.key);
}
return obj;
}
if (isPresent(this._parent)) {
return this._parent._getByKey(key, returnPromise, returnLazy, optional);
private _getByDependency(dep: any, requestor: Key): any {
var special = isPresent(this._ei) ? this._ei.getDependency(dep) : undefinedValue;
if (special !== undefinedValue) {
return special;
} else {
return this._getByKey(dep.key, dep.visibility, dep.optional, requestor);
}
}
private _getByKey(key: Key, depVisibility: any, optional: boolean, requestor: Key): any {
if (key.token === Injector) {
return this;
}
var inj = this;
var ei = this._ei;
// TODO vsavkin remove after DI and EI are merged
var bindingVisibility =
isPresent(ei) && ei.isComponentKey(requestor) ? PUBLIC_AND_PRIVATE : PUBLIC;
var depth = depVisibility.depth;
if (!depVisibility.includeSelf) {
depth -= inj._proto.distanceToParent;
if (isPresent(inj._parent)) {
inj = inj._parent;
} else {
inj = inj._host;
bindingVisibility = depVisibility.crossComponentBoundaries ? PUBLIC : PRIVATE;
}
}
while (inj != null && depth >= 0) {
var obj = inj._strategy.getObjByKeyId(key.id, bindingVisibility);
if (obj !== undefinedValue) return obj;
depth -= inj._proto.distanceToParent;
// we check only one mode with the PRIVATE visibility
if (bindingVisibility === PRIVATE) break;
if (isPresent(inj._parent)) {
inj = inj._parent;
} else {
inj = inj._host;
bindingVisibility = depVisibility.crossComponentBoundaries ? PUBLIC : PRIVATE;
}
}
// TODO vsavkin remove after DI and EI are merged
if (isPresent(ei)) {
var appInj = <Injector>this._ei.appInjector(requestor);
if (optional) {
return appInj.getOptional(key);
} else {
return appInj.get(key);
}
}
if (optional) {
@ -221,149 +724,13 @@ export class Injector {
}
}
_resolveDependencies(key: Key, binding: ResolvedBinding, forceAsync: boolean): List<any> {
try {
var getDependency = d => this._getByKey(d.key, forceAsync || d.asPromise, d.lazy, d.optional);
return ListWrapper.map(binding.dependencies, getDependency);
} catch (e) {
this._clear(key);
if (e instanceof AbstractBindingError) e.addKey(key);
throw e;
}
}
_getInstance(key: Key) {
if (this._instances.length <= key.id) return null;
return ListWrapper.get(this._instances, key.id);
}
_setInstance(key: Key, obj): void { ListWrapper.set(this._instances, key.id, obj); }
_getBinding(key: Key) {
var binding = this._bindings.length <= key.id ? null : ListWrapper.get(this._bindings, key.id);
if (isBlank(binding) && this._defaultBindings) {
var token: any = key.token;
return bind(key.token).toClass(token).resolve();
} else {
return binding;
}
}
_markAsConstructing(key: Key): void { this._setInstance(key, _constructing); }
_clear(key: Key): void { this._setInstance(key, null); }
}
interface _InjectorStrategy {
readFromCache(key: Key);
instantiate(key: Key);
}
class _SyncInjectorStrategy implements _InjectorStrategy {
constructor(private _injector: Injector) {}
readFromCache(key: Key) {
if (key.token === Injector) {
return this._injector;
}
var instance = this._injector._getInstance(key);
if (instance === _constructing) {
throw new CyclicDependencyError(key);
} else if (isPresent(instance) && !_isWaiting(instance)) {
return instance;
} else {
return _notFound;
}
}
instantiate(key: Key) {
var binding = this._injector._getBinding(key);
if (isBlank(binding)) return _notFound;
if (binding.providedAsPromise) throw new AsyncBindingError(key);
// add a marker so we can detect cyclic dependencies
this._injector._markAsConstructing(key);
var deps = this._injector._resolveDependencies(key, binding, false);
return this._createInstance(key, binding, deps);
}
_createInstance(key: Key, binding: ResolvedBinding, deps: List<any>) {
try {
var instance = FunctionWrapper.apply(binding.factory, deps);
this._injector._setInstance(key, instance);
return instance;
} catch (e) {
this._injector._clear(key);
throw new InstantiationError(e, key);
}
// TODO vsavkin remove after DI and EI are merged
getAppInjector(): Injector {
if (isBlank(this._ei)) return this;
return <Injector>this._ei.appInjector(null);
}
}
class _AsyncInjectorStrategy implements _InjectorStrategy {
constructor(private _injector: Injector) {}
readFromCache(key: Key) {
if (key.token === Injector) {
return PromiseWrapper.resolve(this._injector);
}
var instance = this._injector._getInstance(key);
if (instance === _constructing) {
throw new CyclicDependencyError(key);
} else if (_isWaiting(instance)) {
return instance.promise;
} else if (isPresent(instance)) {
return PromiseWrapper.resolve(instance);
} else {
return _notFound;
}
}
instantiate(key: Key) /* Promise?? */ {
var binding = this._injector._getBinding(key);
if (isBlank(binding)) return _notFound;
// add a marker so we can detect cyclic dependencies
this._injector._markAsConstructing(key);
var deps = this._injector._resolveDependencies(key, binding, true);
var depsPromise = PromiseWrapper.all(deps);
var promise = PromiseWrapper.then(depsPromise, null, (e, s) => this._errorHandler(key, e, s))
.then(deps => this._findOrCreate(key, binding, deps))
.then(instance => this._cacheInstance(key, instance));
this._injector._setInstance(key, new _Waiting(promise));
return promise;
}
_errorHandler(key: Key, e, stack): Promise<any> {
if (e instanceof AbstractBindingError) e.addKey(key);
return PromiseWrapper.reject(e, stack);
}
_findOrCreate(key: Key, binding: ResolvedBinding, deps: List<any>) {
try {
var instance = this._injector._getInstance(key);
if (!_isWaiting(instance)) return instance;
return FunctionWrapper.apply(binding.factory, deps);
} catch (e) {
this._injector._clear(key);
throw new InstantiationError(e, key);
}
}
_cacheInstance(key, instance) {
this._injector._setInstance(key, instance);
return instance
}
}
export function resolveBindings(bindings: List<Type | Binding | List<any>>): List<ResolvedBinding> {
var resolvedList = ListWrapper.createFixedSize(bindings.length);
@ -397,9 +764,7 @@ function flattenBindings(bindings: List<ResolvedBinding>): List<ResolvedBinding>
function _createListOfBindings(
flattenedBindings: Map<number, ResolvedBinding>): List<ResolvedBinding> {
var bindings = ListWrapper.createFixedSize(Key.numberOfKeys + 1);
MapWrapper.forEach(flattenedBindings, (v, keyId) => bindings[keyId] = v);
return bindings;
return MapWrapper.values(flattenedBindings);
}
function _flattenBindings(bindings: List<ResolvedBinding | List<any>>,

View File

@ -1,4 +1,5 @@
import {Directive, Parent} from 'angular2/annotations';
import {Directive} from 'angular2/annotations';
import {Parent} from 'angular2/di';
import {ViewContainerRef, ProtoViewRef} from 'angular2/core';
import {isPresent, isBlank, normalizeBlank} from 'angular2/src/facade/lang';
import {ListWrapper, List, MapWrapper, Map} from 'angular2/src/facade/collection';

View File

@ -49,8 +49,8 @@ class MapWrapper {
}
}
static Iterable iterable(Map m) => new IterableMap(m);
static Iterable keys(Map m) => m.keys;
static Iterable values(Map m) => m.values;
static List keys(Map m) => m.keys.toList();
static List values(Map m) => m.values.toList();
}
class StringMapWrapper {

View File

@ -1,5 +1,5 @@
import {Directive, Ancestor, onDestroy, onInit} from 'angular2/angular2';
import {Inject, forwardRef, Binding} from 'angular2/di';
import {Directive, onDestroy, onInit} from 'angular2/angular2';
import {Inject, Ancestor, forwardRef, Binding} from 'angular2/di';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {CONST_EXPR} from 'angular2/src/facade/lang';

View File

@ -2,8 +2,8 @@ import {CONST_EXPR} from 'angular2/src/facade/lang';
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
import {List, StringMapWrapper, StringMap} from 'angular2/src/facade/collection';
import {Directive, Ancestor, onDestroy, onChange, Query, QueryList} from 'angular2/angular2';
import {forwardRef, Binding, Inject} from 'angular2/di';
import {Directive, onDestroy, onChange, Query, QueryList} from 'angular2/angular2';
import {forwardRef, Ancestor, Binding, Inject} from 'angular2/di';
import {ControlContainer} from './control_container';
import {NgControl} from './ng_control';

View File

@ -2,8 +2,8 @@ import {CONST_EXPR} from 'angular2/src/facade/lang';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
import {Directive, Ancestor, onChange, Query, QueryList} from 'angular2/angular2';
import {forwardRef, Binding} from 'angular2/di';
import {Directive, onChange, Query, QueryList} from 'angular2/angular2';
import {forwardRef, Ancestor, Binding} from 'angular2/di';
import {NgControl} from './ng_control';
import {Control} from '../model';

View File

@ -2,8 +2,8 @@ import {CONST_EXPR} from 'angular2/src/facade/lang';
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {Directive, Ancestor, onChange, QueryList, Query} from 'angular2/angular2';
import {forwardRef, Binding} from 'angular2/di';
import {Directive, onChange, QueryList, Query} from 'angular2/angular2';
import {forwardRef, Ancestor, Binding} from 'angular2/di';
import {NgControl} from './ng_control';
import {Control} from '../model';

View File

@ -26,15 +26,17 @@ export class RouterOutlet {
private _componentRef: ComponentRef;
private _elementRef: ElementRef;
private _currentInstruction: Instruction;
private _injector: Injector;
constructor(elementRef: ElementRef, private _loader: DynamicComponentLoader,
private _parentRouter: routerMod.Router, private _injector: Injector,
private _parentRouter: routerMod.Router, _injector: Injector,
@Attribute('name') nameAttr: string) {
// TODO: reintroduce with new // sibling routes
// if (isBlank(nameAttr)) {
// nameAttr = 'default';
//}
this._injector = _injector.getAppInjector();
this._elementRef = elementRef;
this._childRouter = null;

View File

@ -144,6 +144,8 @@ export class TestComponentBuilder {
// TODO(juliemr): can/should this be optional?
DOM.appendChild(doc.body, rootEl);
return this._injector.get(DynamicComponentLoader)
.loadAsRoot(rootComponentType, `#${rootElId}`, this._injector)
.then((componentRef) => { return new RootTestComponent(componentRef); });

View File

@ -85,6 +85,7 @@ export function main() {
inject([AsyncTestCompleter], (async) => {
var refPromise =
bootstrap(HelloRootDirectiveIsNotCmp, testBindings, (e, t) => { throw e; });
PromiseWrapper.then(refPromise, null, (reason) => {
expect(reason.message)
.toContain(
@ -108,24 +109,6 @@ export function main() {
expect(refPromise).not.toBe(null);
});
it('should resolve an injector promise and contain bindings',
inject([AsyncTestCompleter], (async) => {
var refPromise = bootstrap(HelloRootCmp, testBindings);
refPromise.then((ref) => {
expect(ref.injector.get(HelloRootCmp)).toBeAnInstanceOf(HelloRootCmp);
async.done();
});
}));
it('should provide the application component in the injector',
inject([AsyncTestCompleter], (async) => {
var refPromise = bootstrap(HelloRootCmp, testBindings);
refPromise.then((ref) => {
expect(ref.injector.get(HelloRootCmp)).toBeAnInstanceOf(HelloRootCmp);
async.done();
});
}));
it('should display hello world', inject([AsyncTestCompleter], (async) => {
var refPromise = bootstrap(HelloRootCmp, testBindings);
refPromise.then((ref) => {
@ -151,7 +134,7 @@ export function main() {
bootstrap(HelloRootCmp3, [testBindings, bind("appBinding").toValue("BoundValue")]);
refPromise.then((ref) => {
expect(ref.injector.get(HelloRootCmp3).appBinding).toEqual("BoundValue");
expect(ref.hostComponent.appBinding).toEqual("BoundValue");
async.done();
});
}));
@ -161,7 +144,7 @@ export function main() {
var refPromise = bootstrap(HelloRootCmp4, testBindings);
refPromise.then((ref) => {
expect(ref.injector.get(HelloRootCmp4).lc).toBe(ref.injector.get(LifeCycle));
expect(ref.hostComponent.lc).toBe(ref.injector.get(LifeCycle));
async.done();
});
}));
@ -183,7 +166,7 @@ export function main() {
.then((refs: ApplicationRef[]) => {
var registry = refs[0].injector.get(TestabilityRegistry);
var testabilities =
[refs[0].injector.asyncGet(Testability), refs[1].injector.asyncGet(Testability)];
[refs[0].injector.get(Testability), refs[1].injector.get(Testability)];
PromiseWrapper.all(testabilities)
.then((testabilities: Testability[]) => {
expect(registry.findTestabilityInTree(el)).toEqual(testabilities[0]);

View File

@ -34,17 +34,13 @@ import {
} from 'angular2/src/core/compiler/element_injector';
import * as dirAnn from 'angular2/src/core/annotations_impl/annotations';
import {
Parent,
Ancestor,
Unbounded,
Attribute,
Query,
Component,
Directive,
onDestroy
} from 'angular2/annotations';
import * as ngDiAnn from 'angular2/src/core/annotations_impl/visibility';
import {bind, Injector, Binding, resolveBindings, Optional, Inject, Injectable} from 'angular2/di';
import {bind, Injector, Binding, resolveBindings, Optional, Inject, Injectable, Self, Parent, Ancestor, Unbounded, self} from 'angular2/di';
import * as diAnn from 'angular2/src/di/annotations_impl';
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
@ -78,13 +74,16 @@ class DummyElementRef extends SpyObject {
noSuchMethod(m) { return super.noSuchMethod(m); }
}
@Injectable(self)
class SimpleDirective {}
class SimpleService {}
@Injectable(self)
class SomeOtherDirective {}
var _constructionCount = 0;
@Injectable(self)
class CountingDirective {
count;
constructor() {
@ -93,6 +92,7 @@ class CountingDirective {
}
}
@Injectable(self)
class FancyCountingDirective extends CountingDirective {
constructor() { super(); }
}
@ -139,6 +139,12 @@ class NeedsService {
constructor(@Inject("service") service) { this.service = service; }
}
@Injectable()
class NeedsAncestorService {
service: any;
constructor(@Ancestor() @Inject("service") service) { this.service = service; }
}
class HasEventEmitter {
emitter;
constructor() { this.emitter = "emitter"; }
@ -581,7 +587,6 @@ export function main() {
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
});
it("should instantiate hostInjector injectables that have dependencies with set visibility",
function() {
var childInj = parentChildInjectors(
@ -597,7 +602,7 @@ export function main() {
bind('injectable2')
.toFactory(
(val) => `${val}-injectable2`,
[[new diAnn.Inject('injectable1'), new ngDiAnn.Parent()]])
[[new diAnn.Inject('injectable1'), new diAnn.Parent()]])
]
}))]);
expect(childInj.get('injectable2')).toEqual('injectable1-injectable2');
@ -648,7 +653,8 @@ export function main() {
expect(shadowInj.get(NeedsService).service).toEqual('hostService');
});
it("should not instantiate a directive in a view that depends on hostInjector bindings of a decorator directive", () => {
it("should not instantiate a directive in a view that has an ancestor dependency on hostInjector"+
" bindings of a decorator directive", () => {
expect(() => {
hostShadowInjectors(
ListWrapper.concat([
@ -657,7 +663,7 @@ export function main() {
hostInjector: [bind('service').toValue('hostService')]})
)], extraBindings),
ListWrapper.concat([NeedsService], extraBindings)
ListWrapper.concat([NeedsAncestorService], extraBindings)
);
}).toThrowError(new RegExp("No provider for service!"));
});

View File

@ -55,7 +55,8 @@ main() {
});
describe('Error handling', () {
it('should preserve Error stack traces thrown from components', inject([
//TODO: vsavkin reenable this test after merging DI and EI
xit('should preserve Error stack traces thrown from components', inject([
TestComponentBuilder,
AsyncTestCompleter
], (tb, async) {
@ -69,7 +70,8 @@ main() {
});
}));
it('should preserve non-Error stack traces thrown from components', inject([
//TODO: vsavkin reenable this test after merging DI and EI
xit('should preserve non-Error stack traces thrown from components', inject([
TestComponentBuilder,
AsyncTestCompleter
], (tb, async) {

View File

@ -33,7 +33,18 @@ import {
} from 'angular2/src/facade/lang';
import {PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
import {Injector, bind, Injectable, Binding, forwardRef, OpaqueToken, Inject} from 'angular2/di';
import {
Injector,
bind,
Injectable,
Binding,
forwardRef,
OpaqueToken,
Inject,
Parent,
Ancestor,
Unbounded
} from 'angular2/di';
import {
PipeFactory,
PipeRegistry,
@ -45,18 +56,9 @@ import {
ON_PUSH
} from 'angular2/change_detection';
import {
Directive,
Component,
View,
Parent,
Ancestor,
Unbounded,
Attribute,
Query
} from 'angular2/annotations';
import {Directive, Component, View, Attribute, Query} from 'angular2/annotations';
import * as viewAnn from 'angular2/src/core/annotations_impl/view';
import * as visAnn from 'angular2/src/core/annotations_impl/visibility';
import * as visAnn from 'angular2/src/di/annotations_impl';
import {QueryList} from 'angular2/src/core/compiler/query_list';

View File

@ -156,7 +156,7 @@ export function main() {
}
function directiveBinding({metadata}: {metadata?: any} = {}) {
return new DirectiveBinding(Key.get("dummy"), null, [], false, [], [], [], metadata);
return new DirectiveBinding(Key.get("dummy"), null, [], [], [], [], metadata);
}
function createRenderProtoView(elementBinders = null, type: renderApi.ViewType = null) {

View File

@ -210,7 +210,7 @@ export function main() {
});
it('should hydrate the view', () => {
var injector = new Injector([], null, false);
var injector = Injector.resolveAndCreate([]);
manager.createRootHostView(wrapPv(hostProtoView), null, injector);
expect(utils.spy('hydrateRootHostView')).toHaveBeenCalledWith(createdViews[0], injector);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
@ -301,7 +301,7 @@ export function main() {
});
it('should hydrate the view', () => {
var injector = new Injector([], null, false);
var injector = Injector.resolveAndCreate([]);
var contextView =
createView(createProtoView([createEmptyElBinder(), createEmptyElBinder()]));
manager.createViewInContainer(elementRef(parentView, 0), 0, wrapPv(childProtoView),

View File

@ -42,7 +42,7 @@ export function main() {
var directiveResolver;
var utils;
function createInjector() { return new Injector([], null, false); }
function createInjector() { return Injector.resolveAndCreate([]); }
function createDirectiveBinding(type) {
var annotation = directiveResolver.resolve(type);

View File

@ -1,176 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from 'angular2/test_lib';
import {Injector, bind, Key} from 'angular2/di';
import {Inject, InjectPromise, Injectable} from 'angular2/src/di/decorators';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {stringify} from 'angular2/src/facade/lang';
class UserList {}
function fetchUsers() {
return PromiseWrapper.resolve(new UserList());
}
class SynchronousUserList {}
@Injectable()
class UserController {
list: UserList;
constructor(list: UserList) { this.list = list; }
}
@Injectable()
class AsyncUserController {
userList;
constructor(@InjectPromise(UserList) userList) { this.userList = userList; }
}
export function main() {
describe("async injection", function() {
describe("asyncGet", function() {
it('should return a promise', function() {
var injector = Injector.resolveAndCreate([bind(UserList).toAsyncFactory(fetchUsers)]);
var p = injector.asyncGet(UserList);
expect(p).toBePromise();
});
it('should return a promise when the binding is sync', function() {
var injector = Injector.resolveAndCreate([SynchronousUserList]);
var p = injector.asyncGet(SynchronousUserList);
expect(p).toBePromise();
});
it("should return a promise when the binding is sync (from cache)", function() {
var injector = Injector.resolveAndCreate([UserList]);
expect(injector.get(UserList)).toBeAnInstanceOf(UserList);
expect(injector.asyncGet(UserList)).toBePromise();
});
it('should return the injector', inject([AsyncTestCompleter], (async) => {
var injector = Injector.resolveAndCreate([]);
var p = injector.asyncGet(Injector);
p.then(function(injector) {
expect(injector).toBe(injector);
async.done();
});
}));
it('should return a promise when instantiating a sync binding ' +
'with an async dependency',
inject([AsyncTestCompleter], (async) => {
var injector = Injector.resolveAndCreate(
[bind(UserList).toAsyncFactory(fetchUsers), UserController]);
injector.asyncGet(UserController)
.then(function(userController) {
expect(userController).toBeAnInstanceOf(UserController);
expect(userController.list).toBeAnInstanceOf(UserList);
async.done();
});
}));
it("should create only one instance (async + async)",
inject([AsyncTestCompleter], (async) => {
var injector = Injector.resolveAndCreate([bind(UserList).toAsyncFactory(fetchUsers)]);
var ul1 = injector.asyncGet(UserList);
var ul2 = injector.asyncGet(UserList);
PromiseWrapper.all([ul1, ul2])
.then(function(uls) {
expect(uls[0]).toBe(uls[1]);
async.done();
});
}));
it("should create only one instance (sync + async)", inject([AsyncTestCompleter], (async) => {
var injector = Injector.resolveAndCreate([UserList]);
var promise = injector.asyncGet(UserList);
var ul = injector.get(UserList);
expect(promise).toBePromise();
expect(ul).toBeAnInstanceOf(UserList);
promise.then(function(ful) {
expect(ful).toBe(ul);
async.done();
});
}));
it('should show the full path when error happens in a constructor',
inject([AsyncTestCompleter], (async) => {
var injector = Injector.resolveAndCreate([
UserController,
bind(UserList).toAsyncFactory(function() { throw "Broken UserList"; })
]);
var promise = injector.asyncGet(UserController);
PromiseWrapper.then(promise, null, function(e) {
expect(e.message).toContain(
`Error during instantiation of UserList! (${stringify(UserController)} -> UserList)`);
async.done();
});
}));
});
describe("get", function() {
it('should throw when instantiating an async binding', function() {
var injector = Injector.resolveAndCreate([bind(UserList).toAsyncFactory(fetchUsers)]);
expect(() => injector.get(UserList))
.toThrowError(
'Cannot instantiate UserList synchronously. It is provided as a promise!');
});
it('should throw when instantiating a sync binding with an async dependency', function() {
var injector =
Injector.resolveAndCreate([bind(UserList).toAsyncFactory(fetchUsers), UserController]);
expect(() => injector.get(UserController))
.toThrowError(new RegExp(
'Cannot instantiate UserList synchronously. It is provided as a promise!'));
});
it('should not throw when instantiating a sync binding with a resolved async dependency',
inject([AsyncTestCompleter], (async) => {
var injector = Injector.resolveAndCreate(
[bind(UserList).toAsyncFactory(fetchUsers), UserController]);
injector.asyncGet(UserList).then((_) => {
expect(() => { injector.get(UserController); }).not.toThrow();
async.done();
});
}));
it('should resolve synchronously when an async dependency requested as a promise',
function() {
var injector = Injector.resolveAndCreate(
[bind(UserList).toAsyncFactory(fetchUsers), AsyncUserController]);
var controller = injector.get(AsyncUserController);
expect(controller).toBeAnInstanceOf(AsyncUserController);
expect(controller.userList).toBePromise();
});
it('should wrap sync dependencies into promises if required', function() {
var injector = Injector.resolveAndCreate(
[bind(UserList).toFactory(() => new UserList()), AsyncUserController]);
var controller = injector.get(AsyncUserController);
expect(controller).toBeAnInstanceOf(AsyncUserController);
expect(controller.userList).toBePromise();
});
});
});
}

View File

@ -28,14 +28,10 @@ main() {
expect(const Binding(Foo, toFactory: fn).toFactory).toBe(fn);
});
it('can create constant from async factory', () {
expect(const Binding(Foo, toAsyncFactory: fn).toAsyncFactory).toBe(fn);
});
it('can be used in annotation', () {
ClassMirror mirror = reflectType(Annotated);
var bindings = mirror.metadata[0].reflectee.bindings;
expect(bindings.length).toBe(6);
expect(bindings.length).toBe(5);
bindings.forEach((b) {
expect(b).toBeA(Binding);
});
@ -57,7 +53,6 @@ class Annotation {
const Binding(Foo, toClass: Bar),
const Binding(Foo, toValue: 5),
const Binding(Foo, toAlias: Bar),
const Binding(Foo, toFactory: fn),
const Binding(Foo, toAsyncFactory: fn),
const Binding(Foo, toFactory: fn)
])
class Annotated {}

View File

@ -9,13 +9,22 @@ import {
DependencyAnnotation,
Injectable
} from 'angular2/di';
import {Optional, Inject, InjectLazy} from 'angular2/src/di/decorators';
import {Optional, Inject} from 'angular2/src/di/decorators';
import * as ann from 'angular2/src/di/annotations_impl';
class CustomDependencyAnnotation extends DependencyAnnotation {}
class Engine {}
@Injectable(ann.self)
class EngineWithSetVisibility {
}
@Injectable()
class CarNeedsEngineWithSetVisibility {
constructor(engine: EngineWithSetVisibility) {}
}
class BrokenEngine {
constructor() { throw new BaseException("Broken Engine"); }
}
@ -35,12 +44,6 @@ class Car {
constructor(engine: Engine) { this.engine = engine; }
}
@Injectable()
class CarWithLazyEngine {
engineFactory;
constructor(@InjectLazy(Engine) engineFactory) { this.engineFactory = engineFactory; }
}
@Injectable()
class CarWithOptionalEngine {
engine;
@ -236,10 +239,6 @@ export function main() {
expect(() => injector.get(Car))
.toThrowError(
`Cannot instantiate cyclic dependency! (${stringify(Car)} -> ${stringify(Engine)} -> ${stringify(Car)})`);
expect(() => injector.asyncGet(Car))
.toThrowError(
`Cannot instantiate cyclic dependency! (${stringify(Car)} -> ${stringify(Engine)} -> ${stringify(Car)})`);
});
it('should show the full path when error happens in a constructor', () => {
@ -274,25 +273,6 @@ export function main() {
expect(injector.get('null')).toBe(null);
});
describe("default bindings", () => {
it("should be used when no matching binding found", () => {
var injector = Injector.resolveAndCreate([], {defaultBindings: true});
var car = injector.get(Car);
expect(car).toBeAnInstanceOf(Car);
});
it("should use the matching binding when it is available", () => {
var injector =
Injector.resolveAndCreate([bind(Car).toClass(SportsCar)], {defaultBindings: true});
var car = injector.get(Car);
expect(car).toBeAnInstanceOf(SportsCar);
});
});
describe("child", () => {
it('should load instances from parent injector', () => {
var parent = Injector.resolveAndCreate([Engine]);
@ -324,17 +304,6 @@ export function main() {
expect(engineFromChild).toBeAnInstanceOf(TurboEngine);
});
it("should create child injectors without default bindings", () => {
var parent = Injector.resolveAndCreate([], {defaultBindings: true});
var child = parent.resolveAndCreateChild([]);
// child delegates to parent the creation of Car
var childCar = child.get(Car);
var parentCar = parent.get(Car);
expect(childCar).toBe(parentCar);
});
it("should give access to direct parent", () => {
var parent = Injector.resolveAndCreate([]);
var child = parent.resolveAndCreateChild([]);
@ -342,25 +311,6 @@ export function main() {
});
});
describe("lazy", () => {
it("should create dependencies lazily", () => {
var injector = Injector.resolveAndCreate([Engine, CarWithLazyEngine]);
var car = injector.get(CarWithLazyEngine);
expect(car.engineFactory()).toBeAnInstanceOf(Engine);
});
it("should cache instance created lazily", () => {
var injector = Injector.resolveAndCreate([Engine, CarWithLazyEngine]);
var car = injector.get(CarWithLazyEngine);
var e1 = car.engineFactory();
var e2 = car.engineFactory();
expect(e1).toBe(e2);
});
});
describe('resolve', () => {
it('should resolve and flatten', () => {
var bindings = Injector.resolve([Engine, [BrokenEngine]]);
@ -374,20 +324,16 @@ export function main() {
var bindings = Injector.resolve([
forwardRef(() => Engine),
[bind(forwardRef(() => BrokenEngine)).toClass(forwardRef(() => Engine))],
bind(forwardRef(() => String)).toFactory(() => 'OK', [forwardRef(() => Engine)]),
bind(forwardRef(() => DashboardSoftware))
.toAsyncFactory(() => 123, [forwardRef(() => BrokenEngine)])
bind(forwardRef(() => String)).toFactory(() => 'OK', [forwardRef(() => Engine)])
]);
var engineBinding = bindings[Key.get(Engine).id];
var brokenEngineBinding = bindings[Key.get(BrokenEngine).id];
var stringBinding = bindings[Key.get(String).id];
var dashboardSoftwareBinding = bindings[Key.get(DashboardSoftware).id];
var engineBinding = bindings[0];
var brokenEngineBinding = bindings[1];
var stringBinding = bindings[2];
expect(engineBinding.factory() instanceof Engine).toBe(true);
expect(brokenEngineBinding.factory() instanceof Engine).toBe(true);
expect(stringBinding.dependencies[0].key).toEqual(Key.get(Engine));
expect(dashboardSoftwareBinding.dependencies[0].key).toEqual(Key.get(BrokenEngine));
});
it('should support overriding factory dependencies with dependency annotations', () => {
@ -396,11 +342,25 @@ export function main() {
.toFactory((e) => "result",
[[new ann.Inject("dep"), new CustomDependencyAnnotation()]])
]);
var binding = bindings[Key.get("token").id];
var binding = bindings[0];
expect(binding.dependencies[0].key.token).toEqual("dep");
expect(binding.dependencies[0].properties).toEqual([new CustomDependencyAnnotation()]);
});
});
describe("default visibility", () => {
it("should use the provided visibility", () => {
var bindings = Injector.resolve([CarNeedsEngineWithSetVisibility, EngineWithSetVisibility]);
var carBinding = bindings[0];
expect(carBinding.dependencies[0].visibility).toEqual(ann.self);
});
it("should set the default visibility to unbounded", () => {
var bindings = Injector.resolve([Car, Engine]);
var carBinding = bindings[0];
expect(carBinding.dependencies[0].visibility).toEqual(ann.unbounded);
});
});
});
}

View File

@ -56,7 +56,7 @@ export function main() {
var router = applicationRef.hostComponent.router;
PromiseWrapper.catchError(router.navigate('/cause-error'), (error) => {
expect(el).toHaveText('outer { oh no }');
expect(error.message).toBe('oops!');
expect(error.message).toContain('oops!');
async.done();
});
});
@ -89,7 +89,6 @@ export function main() {
router.navigate('/parent/child');
});
}));
// TODO: add a test in which the child component has bindings
});
}
@ -107,14 +106,12 @@ class AppCmp {
constructor(public router: Router, public location: LocationStrategy) {}
}
@Component({selector: 'parent-cmp'})
@View({template: `parent { <router-outlet></router-outlet> }`, directives: routerDirectives})
@RouteConfig([{path: '/child', component: HelloCmp}])
class ParentCmp {
}
@Component({selector: 'app-cmp'})
@View({template: `root { <router-outlet></router-outlet> }`, directives: routerDirectives})
@RouteConfig([{path: '/parent/...', component: ParentCmp}])

View File

@ -23,7 +23,7 @@ export function main() {
bootstrap(AppComponent)
.then((ref) => {
var injector = ref.injector;
var app: AppComponent = injector.get(AppComponent);
var app: AppComponent = ref.hostComponent;
var lifeCycle = injector.get(LifeCycle);
bindAction('#reset', function() {

View File

@ -125,7 +125,7 @@ export function main() {
bootstrap(AppComponent, _createBindings())
.then((ref) => {
var injector = ref.injector;
app = injector.get(AppComponent);
app = ref.hostComponent;
lifecycle = injector.get(LifeCycle);
bindAction('#ng2DestroyDom', ng2DestroyDom);
bindAction('#ng2CreateDom', ng2CreateDom);

View File

@ -85,7 +85,6 @@ export function main() {
function ng2CreateDom() {
var values = count++ % 2 == 0 ? ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*'] :
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', '-'];
app.initData = buildTree(maxDepth, values, 0);
lifeCycle.tick();
}
@ -98,7 +97,7 @@ export function main() {
var injector = ref.injector;
lifeCycle = injector.get(LifeCycle);
app = injector.get(AppComponent);
app = ref.hostComponent;
bindAction('#ng2DestroyDom', ng2DestroyDom);
bindAction('#ng2CreateDom', ng2CreateDom);
bindAction('#ng2UpdateDomProfile', profile(ng2CreateDom, noop, 'ng2-update'));

View File

@ -8,10 +8,10 @@ export class MultiMetric extends Metric {
static createBindings(childTokens): List<Binding> {
return [
bind(_CHILDREN)
.toAsyncFactory((injector) => PromiseWrapper.all(
ListWrapper.map(childTokens, (token) => injector.asyncGet(token))),
.toFactory(
(injector: Injector) => ListWrapper.map(childTokens, (token) => injector.get(token)),
[Injector]),
bind(MultiMetric).toFactory((children) => new MultiMetric(children), [_CHILDREN])
bind(MultiMetric).toFactory(children => new MultiMetric(children), [_CHILDREN])
];
}

View File

@ -9,10 +9,10 @@ export class MultiReporter extends Reporter {
static createBindings(childTokens: List<any>): List<Binding> {
return [
bind(_CHILDREN)
.toAsyncFactory((injector) => PromiseWrapper.all(
ListWrapper.map(childTokens, (token) => injector.asyncGet(token))),
.toFactory(
(injector: Injector) => ListWrapper.map(childTokens, (token) => injector.get(token)),
[Injector]),
bind(MultiReporter).toFactory((children) => new MultiReporter(children), [_CHILDREN])
bind(MultiReporter).toFactory(children => new MultiReporter(children), [_CHILDREN])
];
}

View File

@ -1,7 +1,7 @@
import {Injector, bind, Binding} from 'angular2/di';
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {Promise} from 'angular2/src/facade/async';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {Sampler, SampleState} from './sampler';
import {ConsoleReporter} from './reporter/console_reporter';
@ -50,9 +50,31 @@ export class Runner {
if (isPresent(bindings)) {
sampleBindings.push(bindings);
}
return Injector.resolveAndCreate(sampleBindings)
.asyncGet(Sampler)
.then((sampler) => sampler.sample());
var inj = Injector.resolveAndCreate(sampleBindings);
var adapter = inj.get(WebDriverAdapter);
return PromiseWrapper
.all([adapter.capabilities(), adapter.executeScript('return window.navigator.userAgent;')])
.then((args) => {
var capabilities = args[0];
var userAgent = args[1];
// This might still create instances twice. We are creating a new injector with all the
// bindings.
// Only WebDriverAdapter is reused.
// TODO vsavkin consider changing it when toAsyncFactory is added back or when child
// injectors are handled better.
var injector = Injector.resolveAndCreate([
sampleBindings,
bind(Options.CAPABILITIES).toValue(capabilities),
bind(Options.USER_AGENT).toValue(userAgent),
bind(WebDriverAdapter).toValue(adapter)
]);
var sampler = injector.get(Sampler);
return sampler.sample();
});
}
}
@ -74,10 +96,4 @@ var _DEFAULT_BINDINGS = [
Validator.bindTo(RegressionSlopeValidator),
WebDriverExtension.bindTo([ChromeDriverExtension, FirefoxDriverExtension, IOsDriverExtension]),
Metric.bindTo(MultiMetric),
bind(Options.CAPABILITIES)
.toAsyncFactory((adapter) => adapter.capabilities(), [WebDriverAdapter]),
bind(Options.USER_AGENT)
.toAsyncFactory((adapter) => adapter.executeScript('return window.navigator.userAgent;'),
[WebDriverAdapter])
];

View File

@ -14,10 +14,10 @@ import {Options} from './common_options';
@ABSTRACT()
export class WebDriverExtension {
static bindTo(childTokens): List<Binding> {
return [
var res = [
bind(_CHILDREN)
.toAsyncFactory((injector) => PromiseWrapper.all(
ListWrapper.map(childTokens, (token) => injector.asyncGet(token))),
.toFactory(
(injector: Injector) => ListWrapper.map(childTokens, (token) => injector.get(token)),
[Injector]),
bind(WebDriverExtension)
.toFactory(
@ -35,6 +35,7 @@ export class WebDriverExtension {
},
[_CHILDREN, Options.CAPABILITIES])
];
return res;
}
gc(): Promise<any> { throw new BaseException('NYI'); }

View File

@ -18,15 +18,15 @@ import {Metric, MultiMetric, bind, Injector} from 'benchpress/common';
export function main() {
function createMetric(ids) {
return Injector.resolveAndCreate([
var m = Injector.resolveAndCreate([
ListWrapper.map(ids, (id) => bind(id).toValue(new MockMetric(id))),
MultiMetric.createBindings(ids)
])
.asyncGet(MultiMetric);
.get(MultiMetric);
return PromiseWrapper.resolve(m);
}
describe('multi metric', () => {
it('should merge descriptions', inject([AsyncTestCompleter], (async) => {
createMetric(['m1', 'm2'])
.then((m) => {

View File

@ -19,11 +19,12 @@ import {Reporter, MultiReporter, bind, Injector, MeasureValues} from 'benchpress
export function main() {
function createReporters(ids) {
return Injector.resolveAndCreate([
var r = Injector.resolveAndCreate([
ListWrapper.map(ids, (id) => bind(id).toValue(new MockReporter(id))),
MultiReporter.createBindings(ids)
])
.asyncGet(MultiReporter);
.get(MultiReporter);
return PromiseWrapper.resolve(r);
}
describe('multi reporter', () => {

View File

@ -52,7 +52,7 @@ export function main() {
it('should set SampleDescription.id', inject([AsyncTestCompleter], (async) => {
createRunner()
.sample({id: 'someId'})
.then((_) => injector.asyncGet(SampleDescription))
.then((_) => injector.get(SampleDescription))
.then((desc) => {
expect(desc.id).toBe('someId');
async.done();
@ -62,9 +62,8 @@ export function main() {
it('should merge SampleDescription.description', inject([AsyncTestCompleter], (async) => {
createRunner([bind(Options.DEFAULT_DESCRIPTION).toValue({'a': 1})])
.sample({id: 'someId', bindings: [bind(Options.SAMPLE_DESCRIPTION).toValue({'b': 2})]})
.then((_) => injector.asyncGet(SampleDescription))
.then((_) => injector.get(SampleDescription))
.then((desc) => {
expect(desc.description)
.toEqual(
{'forceGc': false, 'userAgent': 'someUserAgent', 'a': 1, 'b': 2, 'v': 11});
@ -76,7 +75,7 @@ export function main() {
inject([AsyncTestCompleter], (async) => {
createRunner()
.sample({id: 'someId'})
.then((_) => injector.asyncGet(SampleDescription))
.then((_) => injector.get(SampleDescription))
.then((desc) => {
expect(desc.metrics).toEqual({'m1': 'some metric'});
@ -125,10 +124,10 @@ export function main() {
.toValue({'a': 2}),
]
})
.then((_) => injector.asyncGet(SampleDescription))
.then((_) => injector.get(SampleDescription))
.then((desc) => {
expect(injector.get(SampleDescription).description['a']).toBe(2);
expect(desc.description['a']).toBe(2);
async.done();
});
@ -139,6 +138,7 @@ export function main() {
class MockWebDriverAdapter extends WebDriverAdapter {
executeScript(script): Promise<string> { return PromiseWrapper.resolve('someUserAgent'); }
capabilities() { return null; }
}
class MockValidator extends Validator {

View File

@ -19,12 +19,14 @@ import {WebDriverExtension, bind, Injector, Options} from 'benchpress/common';
export function main() {
function createExtension(ids, caps) {
return PromiseWrapper.wrap(() => {
return Injector.resolveAndCreate([
ListWrapper.map(ids, (id) => bind(id).toValue(new MockExtension(id))),
bind(Options.CAPABILITIES).toValue(caps),
WebDriverExtension.bindTo(ids)
])
.asyncGet(WebDriverExtension);
.get(WebDriverExtension);
});
}
describe('WebDriverExtension.bindTo', () => {
@ -44,7 +46,6 @@ export function main() {
async.done();
});
}));
});
}