refactor(di): removed @Parent

BREAKING CHANGE
    The @Parent annotation has been removed. Use @Ancestor instead.
    @Parent was used to enforce a particular DOM structure (e.g., a pane component is a direct child of the tabs component).
    DI is not the right mechanism to do it. We should enforce it using schema instead.
This commit is contained in:
vsavkin 2015-07-13 15:57:06 -07:00
parent a472eacc07
commit 6f4a39c337
16 changed files with 269 additions and 256 deletions

View File

@ -10,7 +10,6 @@ export {
InjectableMetadata, InjectableMetadata,
VisibilityMetadata, VisibilityMetadata,
SelfMetadata, SelfMetadata,
ParentMetadata,
AncestorMetadata, AncestorMetadata,
UnboundedMetadata, UnboundedMetadata,
DependencyMetadata, DependencyMetadata,
@ -24,12 +23,12 @@ export {forwardRef, resolveForwardRef, ForwardRefFn} from './src/di/forward_ref'
export { export {
Injector, Injector,
ProtoInjector, ProtoInjector,
BindingWithVisibility,
DependencyProvider, DependencyProvider,
PUBLIC_AND_PRIVATE, PUBLIC_AND_PRIVATE,
PUBLIC, PUBLIC,
PRIVATE, PRIVATE,
undefinedValue undefinedValue
} from './src/di/injector'; } from './src/di/injector';
export {Binding, BindingBuilder, ResolvedBinding, Dependency, bind} from './src/di/binding'; export {Binding, BindingBuilder, ResolvedBinding, Dependency, bind} from './src/di/binding';
export {Key, KeyRegistry, TypeLiteral} from './src/di/key'; export {Key, KeyRegistry, TypeLiteral} from './src/di/key';

View File

@ -262,7 +262,6 @@ There are five kinds of visibilities:
* (no annotation): Inject dependent directives only if they are on the current element. * (no annotation): Inject dependent directives only if they are on the current element.
* `@ancestor`: Inject a directive if it is at any element above the current element. * `@ancestor`: Inject a directive if it is at any element above the current element.
* `@parent`: Inject a directive which is a direct parent of the current element.
* `@child`: Inject a list of direct children which match a given type. (Used with `Query`) * `@child`: Inject a list of direct children which match a given type. (Used with `Query`)
* `@descendant`: Inject a list of any children which match a given type. (Used with `Query`) * `@descendant`: Inject a list of any children which match a given type. (Used with `Query`)
@ -301,7 +300,7 @@ class FieldSet { |
class Field { | class Field { |
constructor( | constructor( |
@ancestor field:Form, | @ancestor field:Form, |
@parent field:FieldSet, | @ancestor field:FieldSet, |
) { ... } | ) { ... } |
} | } |
| |
@ -337,7 +336,7 @@ Shadow DOM provides an encapsulation for components, so as a general rule it doe
}) })
class Kid { class Kid {
constructor( constructor(
@Parent() dad:Dad, @Ancestor() dad:Dad,
@Optional() grandpa:Grandpa @Optional() grandpa:Grandpa
) { ) {
this.name = 'Billy'; this.name = 'Billy';
@ -354,7 +353,7 @@ class Kid {
directives: [Kid] directives: [Kid]
}) })
class Dad { class Dad {
constructor(@Parent() dad:Grandpa) { constructor(@Ancestor() dad:Grandpa) {
this.name = 'Joe Jr'; this.name = 'Joe Jr';
this.dad = dad.name; this.dad = dad.name;
} }

View File

@ -58,8 +58,6 @@ import {DEFAULT} from 'angular2/change_detection';
* Shadow DOM root. Current element is not included in the resolution, therefore even if it could * Shadow DOM root. Current element is not included in the resolution, therefore even if it could
* resolve it, it will * resolve it, it will
* be ignored. * be ignored.
* - `@Parent() directive:DirectiveType`: any directive that matches the type on a direct parent
* element only.
* - `@Query(DirectiveType) query:QueryList<DirectiveType>`: A live collection of direct child * - `@Query(DirectiveType) query:QueryList<DirectiveType>`: A live collection of direct child
* directives. * directives.
* - `@QueryDescendants(DirectiveType) query:QueryList<DirectiveType>`: A live collection of any * - `@QueryDescendants(DirectiveType) query:QueryList<DirectiveType>`: A live collection of any
@ -163,27 +161,6 @@ import {DEFAULT} from 'angular2/change_detection';
* This directive would be instantiated with `Dependency` declared at the same element, in this case * This directive would be instantiated with `Dependency` declared at the same element, in this case
* `dependency="3"`. * `dependency="3"`.
* *
*
* ### Injecting a directive from a direct parent element
*
* Directives can inject other directives declared on a direct parent element. By definition, a
* directive with a
* `@Parent` annotation does not attempt to resolve dependencies for the current element, even if
* this would satisfy
* the dependency.
*
* ```
* @Directive({ selector: '[my-directive]' })
* class MyDirective {
* constructor(@Parent() dependency: Dependency) {
* expect(dependency.id).toEqual(2);
* }
* }
* ```
* This directive would be instantiated with `Dependency` declared at the parent element, in this
* case `dependency="2"`.
*
*
* ### Injecting a directive from any ancestor elements * ### Injecting a directive from any ancestor elements
* *
* Directives can inject other directives declared on any ancestor element (in the current Shadow * Directives can inject other directives declared on any ancestor element (in the current Shadow
@ -201,8 +178,8 @@ import {DEFAULT} from 'angular2/change_detection';
* } * }
* ``` * ```
* *
* Unlike the `@Parent` which only checks the parent, `@Ancestor` checks the parent, as well as its * `@Ancestor` checks the parent, as well as its parents recursively. If `dependency="2"` didn't
* parents recursively. If `dependency="2"` didn't exist on the direct parent, this injection would * exist on the direct parent, this injection would
* have returned * have returned
* `dependency="1"`. * `dependency="1"`.
* *

View File

@ -397,7 +397,7 @@ export class ProtoElementInjector {
public directiveVariableBindings: Map<string, number>) { public directiveVariableBindings: Map<string, number>) {
var length = bwv.length; var length = bwv.length;
this.protoInjector = new ProtoInjector(bwv, distanceToParent); this.protoInjector = new ProtoInjector(bwv);
this.eventEmitterAccessors = ListWrapper.createFixedSize(length); this.eventEmitterAccessors = ListWrapper.createFixedSize(length);
this.hostActionAccessors = ListWrapper.createFixedSize(length); this.hostActionAccessors = ListWrapper.createFixedSize(length);

View File

@ -31,13 +31,6 @@ class Self extends SelfMetadata {
const Self(): super(); const Self(): super();
} }
/**
* {@link ParentMetadata}.
*/
class Parent extends ParentMetadata {
const Parent({bool self}): super(self:self);
}
/** /**
* {@link AncestorMetadata}. * {@link AncestorMetadata}.
*/ */

View File

@ -4,7 +4,6 @@ import {
InjectableMetadata, InjectableMetadata,
SelfMetadata, SelfMetadata,
VisibilityMetadata, VisibilityMetadata,
ParentMetadata,
AncestorMetadata, AncestorMetadata,
UnboundedMetadata UnboundedMetadata
} from './metadata'; } from './metadata';
@ -42,14 +41,6 @@ export interface SelfFactory {
new (): SelfMetadata; new (): SelfMetadata;
} }
/**
* Factory for creating {@link ParentMetadata}.
*/
export interface ParentFactory {
(visibility?: {self: boolean}): any;
new (visibility?: {self: boolean}): ParentMetadata;
}
/** /**
* Factory for creating {@link AncestorMetadata}. * Factory for creating {@link AncestorMetadata}.
*/ */
@ -86,11 +77,6 @@ export var Injectable: InjectableFactory = <InjectableFactory>makeDecorator(Inje
*/ */
export var Self: SelfFactory = makeParamDecorator(SelfMetadata); export var Self: SelfFactory = makeParamDecorator(SelfMetadata);
/**
* Factory for creating {@link ParentMetadata}.
*/
export var Parent: ParentFactory = makeParamDecorator(ParentMetadata);
/** /**
* Factory for creating {@link AncestorMetadata}. * Factory for creating {@link AncestorMetadata}.
*/ */

View File

@ -14,7 +14,7 @@ import {
import {FunctionWrapper, Type, isPresent, isBlank, CONST_EXPR} from 'angular2/src/facade/lang'; import {FunctionWrapper, Type, isPresent, isBlank, CONST_EXPR} from 'angular2/src/facade/lang';
import {Key} from './key'; import {Key} from './key';
import {resolveForwardRef} from './forward_ref'; import {resolveForwardRef} from './forward_ref';
import {VisibilityMetadata, DEFAULT_VISIBILITY} from './metadata'; import {VisibilityMetadata, DEFAULT_VISIBILITY, SelfMetadata, AncestorMetadata} from './metadata';
const _constructing = CONST_EXPR(new Object()); const _constructing = CONST_EXPR(new Object());
const _notFound = CONST_EXPR(new Object()); const _notFound = CONST_EXPR(new Object());
@ -175,7 +175,7 @@ export class ProtoInjectorDynamicStrategy implements ProtoInjectorStrategy {
export class ProtoInjector { export class ProtoInjector {
_strategy: ProtoInjectorStrategy; _strategy: ProtoInjectorStrategy;
constructor(bwv: BindingWithVisibility[], public distanceToParent: number) { constructor(bwv: BindingWithVisibility[]) {
this._strategy = bwv.length > _MAX_CONSTRUCTION_COUNTER ? this._strategy = bwv.length > _MAX_CONSTRUCTION_COUNTER ?
new ProtoInjectorDynamicStrategy(this, bwv) : new ProtoInjectorDynamicStrategy(this, bwv) :
new ProtoInjectorInlineStrategy(this, bwv); new ProtoInjectorInlineStrategy(this, bwv);
@ -459,7 +459,7 @@ export class Injector {
static fromResolvedBindings(bindings: List<ResolvedBinding>, static fromResolvedBindings(bindings: List<ResolvedBinding>,
depProvider: DependencyProvider = null): Injector { depProvider: DependencyProvider = null): Injector {
var bd = bindings.map(b => new BindingWithVisibility(b, PUBLIC)); var bd = bindings.map(b => new BindingWithVisibility(b, PUBLIC));
var proto = new ProtoInjector(bd, 0); var proto = new ProtoInjector(bd);
var inj = new Injector(proto, null, depProvider); var inj = new Injector(proto, null, depProvider);
return inj; return inj;
} }
@ -542,7 +542,7 @@ export class Injector {
createChildFromResolved(bindings: List<ResolvedBinding>, createChildFromResolved(bindings: List<ResolvedBinding>,
depProvider: DependencyProvider = null): Injector { depProvider: DependencyProvider = null): Injector {
var bd = bindings.map(b => new BindingWithVisibility(b, PUBLIC)); var bd = bindings.map(b => new BindingWithVisibility(b, PUBLIC));
var proto = new ProtoInjector(bd, 1); var proto = new ProtoInjector(bd);
var inj = new Injector(proto, null, depProvider); var inj = new Injector(proto, null, depProvider);
inj._parent = this; inj._parent = this;
return inj; return inj;
@ -678,49 +678,79 @@ export class Injector {
return this; return this;
} }
var inj = this; if (depVisibility instanceof SelfMetadata) {
var lastInjector = false; return this._getByKeySelf(key, optional, bindingVisibility);
var depth = depVisibility.depth;
if (!depVisibility.includeSelf) { } else if (depVisibility instanceof AncestorMetadata) {
depth -= inj._proto.distanceToParent; return this._getByKeyAncestor(key, optional, bindingVisibility, depVisibility.includeSelf);
if (inj._isBoundary) {
if (depVisibility.crossBoundaries) {
bindingVisibility = PUBLIC_AND_PRIVATE;
} else { } else {
bindingVisibility = PRIVATE; return this._getByKeyUnbounded(key, optional, bindingVisibility, depVisibility.includeSelf);
lastInjector = true;
} }
} }
inj = inj._parent;
}
while (inj != null && depth >= 0) {
var obj = inj._strategy.getObjByKeyId(key.id, bindingVisibility);
if (obj !== undefinedValue) return obj;
depth -= inj._proto.distanceToParent;
if (lastInjector) break;
if (inj._isBoundary) {
if (depVisibility.crossBoundaries) {
bindingVisibility = PUBLIC_AND_PRIVATE;
} else {
bindingVisibility = PRIVATE;
lastInjector = true;
}
}
inj = inj._parent;
}
_throwOrNull(key: Key, optional: boolean): any {
if (optional) { if (optional) {
return null; return null;
} else { } else {
throw new NoBindingError(key); throw new NoBindingError(key);
} }
} }
_getByKeySelf(key: Key, optional: boolean, bindingVisibility: number): any {
var obj = this._strategy.getObjByKeyId(key.id, bindingVisibility);
return (obj !== undefinedValue) ? obj : this._throwOrNull(key, optional);
}
_getByKeyAncestor(key: Key, optional: boolean, bindingVisibility: number,
includeSelf: boolean): any {
var inj = this;
if (!includeSelf) {
if (inj._isBoundary) {
return this._getPrivateDependency(key, optional, inj);
} else {
inj = inj._parent;
}
}
while (inj != null) {
var obj = inj._strategy.getObjByKeyId(key.id, bindingVisibility);
if (obj !== undefinedValue) return obj;
if (isPresent(inj._parent) && inj._isBoundary) {
return this._getPrivateDependency(key, optional, inj);
} else {
inj = inj._parent;
}
}
return this._throwOrNull(key, optional);
}
_getPrivateDependency(key: Key, optional: boolean, inj: Injector): any {
var obj = inj._parent._strategy.getObjByKeyId(key.id, PRIVATE);
return (obj !== undefinedValue) ? obj : this._throwOrNull(key, optional);
}
_getByKeyUnbounded(key: Key, optional: boolean, bindingVisibility: number,
includeSelf: boolean): any {
var inj = this;
if (!includeSelf) {
bindingVisibility = inj._isBoundary ? PUBLIC_AND_PRIVATE : PUBLIC;
inj = inj._parent;
}
while (inj != null) {
var obj = inj._strategy.getObjByKeyId(key.id, bindingVisibility);
if (obj !== undefinedValue) return obj;
bindingVisibility = inj._isBoundary ? PUBLIC_AND_PRIVATE : PUBLIC;
inj = inj._parent;
}
return this._throwOrNull(key, optional);
}
} }
var INJECTOR_KEY = Key.get(Injector); var INJECTOR_KEY = Key.get(Injector);

View File

@ -42,21 +42,21 @@ export class OptionalMetadata {
* For example: * For example:
* *
* ``` * ```
* class Parent extends DependencyMetadata {} * class Exclude extends DependencyMetadata {}
* class NotDependencyProperty {} * class NotDependencyProperty {}
* *
* class AComponent { * class AComponent {
* constructor(@Parent @NotDependencyProperty aService:AService) {} * constructor(@Exclude @NotDependencyProperty aService:AService) {}
* } * }
* ``` * ```
* *
* will create the following dependency: * will create the following dependency:
* *
* ``` * ```
* new Dependency(Key.get(AService), [new Parent()]) * new Dependency(Key.get(AService), [new Exclude()])
* ``` * ```
* *
* The framework can use `new Parent()` to handle the `aService` dependency * The framework can use `new Exclude()` to handle the `aService` dependency
* in a specific way. * in a specific way.
*/ */
@CONST() @CONST()
@ -85,17 +85,16 @@ export class InjectableMetadata {
/** /**
* Specifies how injector should resolve a dependency. * Specifies how injector should resolve a dependency.
* *
* See {@link Self}, {@link Parent}, {@link Ancestor}, {@link Unbounded}. * See {@link Self}, {@link Ancestor}, {@link Unbounded}.
*/ */
@CONST() @CONST()
export class VisibilityMetadata { export class VisibilityMetadata {
constructor(public depth: number, public crossBoundaries: boolean, public _includeSelf: boolean) { constructor(public crossBoundaries: boolean, public _includeSelf: boolean) {}
}
get includeSelf(): boolean { return isBlank(this._includeSelf) ? false : this._includeSelf; } get includeSelf(): boolean { return isBlank(this._includeSelf) ? false : this._includeSelf; }
toString(): string { toString(): string {
return `@Visibility(depth: ${this.depth}, crossBoundaries: ${this.crossBoundaries}, includeSelf: ${this.includeSelf}})`; return `@Visibility(crossBoundaries: ${this.crossBoundaries}, includeSelf: ${this.includeSelf}})`;
} }
} }
@ -119,46 +118,10 @@ export class VisibilityMetadata {
*/ */
@CONST() @CONST()
export class SelfMetadata extends VisibilityMetadata { export class SelfMetadata extends VisibilityMetadata {
constructor() { super(0, false, true); } constructor() { super(false, true); }
toString(): string { return `@Self()`; } toString(): string { return `@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) {}
* }
* ```
*/
@CONST()
export class ParentMetadata extends VisibilityMetadata {
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. * Specifies that an injector should retrieve a dependency from any ancestor from the same boundary.
* *
@ -192,7 +155,7 @@ export class ParentMetadata extends VisibilityMetadata {
*/ */
@CONST() @CONST()
export class AncestorMetadata extends VisibilityMetadata { export class AncestorMetadata extends VisibilityMetadata {
constructor({self}: {self?: boolean} = {}) { super(999999, false, self); } constructor({self}: {self?: boolean} = {}) { super(false, self); }
toString(): string { return `@Ancestor(self: ${this.includeSelf}})`; } toString(): string { return `@Ancestor(self: ${this.includeSelf}})`; }
} }
@ -229,7 +192,7 @@ export class AncestorMetadata extends VisibilityMetadata {
*/ */
@CONST() @CONST()
export class UnboundedMetadata extends VisibilityMetadata { export class UnboundedMetadata extends VisibilityMetadata {
constructor({self}: {self?: boolean} = {}) { super(999999, true, self); } constructor({self}: {self?: boolean} = {}) { super(true, self); }
toString(): string { return `@Unbounded(self: ${this.includeSelf}})`; } toString(): string { return `@Unbounded(self: ${this.includeSelf}})`; }
} }

View File

@ -1,5 +1,5 @@
import {Directive} from 'angular2/annotations'; import {Directive} from 'angular2/annotations';
import {Parent} from 'angular2/di'; import {Ancestor} from 'angular2/di';
import {ViewContainerRef, TemplateRef} from 'angular2/core'; import {ViewContainerRef, TemplateRef} from 'angular2/core';
import {isPresent, isBlank, normalizeBlank} from 'angular2/src/facade/lang'; import {isPresent, isBlank, normalizeBlank} from 'angular2/src/facade/lang';
import {ListWrapper, List, MapWrapper, Map} from 'angular2/src/facade/collection'; import {ListWrapper, List, MapWrapper, Map} from 'angular2/src/facade/collection';
@ -157,7 +157,7 @@ export class NgSwitchWhen {
_view: SwitchView; _view: SwitchView;
constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef, constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef,
@Parent() sswitch: NgSwitch) { @Ancestor() sswitch: NgSwitch) {
// `_whenDefault` is used as a marker for a not yet initialized value // `_whenDefault` is used as a marker for a not yet initialized value
this._value = _whenDefault; this._value = _whenDefault;
this._switch = sswitch; this._switch = sswitch;
@ -187,7 +187,7 @@ export class NgSwitchWhen {
@Directive({selector: '[ng-switch-default]'}) @Directive({selector: '[ng-switch-default]'})
export class NgSwitchDefault { export class NgSwitchDefault {
constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef, constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef,
@Parent() sswitch: NgSwitch) { @Ancestor() sswitch: NgSwitch) {
sswitch._registerView(_whenDefault, new SwitchView(viewContainer, templateRef)); sswitch._registerView(_whenDefault, new SwitchView(viewContainer, templateRef));
} }
} }

View File

@ -40,7 +40,7 @@ import {
Directive, Directive,
LifecycleEvent LifecycleEvent
} from 'angular2/annotations'; } from 'angular2/annotations';
import {bind, Injector, Binding, Optional, Inject, Injectable, Self, Parent, Ancestor, Unbounded, InjectMetadata, ParentMetadata} from 'angular2/di'; import {bind, Injector, Binding, Optional, Inject, Injectable, Self, Ancestor, Unbounded, InjectMetadata, AncestorMetadata} from 'angular2/di';
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view'; import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref'; import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
import {TemplateRef} from 'angular2/src/core/compiler/template_ref'; import {TemplateRef} from 'angular2/src/core/compiler/template_ref';
@ -102,18 +102,6 @@ class OptionallyNeedsDirective {
constructor(@Self() @Optional() dependency: SimpleDirective) { this.dependency = dependency; } constructor(@Self() @Optional() dependency: SimpleDirective) { this.dependency = dependency; }
} }
@Injectable()
class NeedsDirectiveFromParent {
dependency: SimpleDirective;
constructor(@Parent() dependency: SimpleDirective) { this.dependency = dependency; }
}
@Injectable()
class NeedsDirectiveFromParentOrSelf {
dependency: SimpleDirective;
constructor(@Parent({self: true}) dependency: SimpleDirective) { this.dependency = dependency; }
}
@Injectable() @Injectable()
class NeedsDirectiveFromAncestor { class NeedsDirectiveFromAncestor {
dependency: SimpleDirective; dependency: SimpleDirective;
@ -609,7 +597,7 @@ export function main() {
bind('injectable2') bind('injectable2')
.toFactory( .toFactory(
(val) => `${val}-injectable2`, (val) => `${val}-injectable2`,
[[new InjectMetadata('injectable1'), new ParentMetadata()]]) [[new InjectMetadata('injectable1'), new AncestorMetadata()]])
] ]
}))]); }))]);
expect(childInj.get('injectable2')).toEqual('injectable1-injectable2'); expect(childInj.get('injectable2')).toEqual('injectable1-injectable2');
@ -775,31 +763,6 @@ export function main() {
expect(inj.get(NeedsTemplateRef).templateRef).toEqual(templateRef); expect(inj.get(NeedsTemplateRef).templateRef).toEqual(templateRef);
}); });
it("should get directives from parent", () => {
var child = parentChildInjectors(ListWrapper.concat([SimpleDirective], extraBindings),
[NeedsDirectiveFromParent]);
var d = child.get(NeedsDirectiveFromParent);
expect(d).toBeAnInstanceOf(NeedsDirectiveFromParent);
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
});
it("should not return parent's directives on self by default", () => {
expect(() => {
injector(ListWrapper.concat([SimpleDirective, NeedsDirectiveFromParent], extraBindings));
}).toThrowError(containsRegexp(`No provider for ${stringify(SimpleDirective) }`));
});
it("should return parent's directives on self when explicitly specified", () => {
var inj = injector(ListWrapper.concat([SimpleDirective, NeedsDirectiveFromParentOrSelf], extraBindings));
var d = inj.get(NeedsDirectiveFromParentOrSelf);
expect(d).toBeAnInstanceOf(NeedsDirectiveFromParentOrSelf);
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
});
it("should get directives from ancestor", () => { it("should get directives from ancestor", () => {
var child = parentChildInjectors(ListWrapper.concat([SimpleDirective], extraBindings), var child = parentChildInjectors(ListWrapper.concat([SimpleDirective], extraBindings),
[NeedsDirectiveFromAncestor]); [NeedsDirectiveFromAncestor]);
@ -822,9 +785,9 @@ export function main() {
}); });
it("should throw when a dependency cannot be resolved", () => { it("should throw when a dependency cannot be resolved", () => {
expect(() => injector(ListWrapper.concat([NeedsDirectiveFromParent], extraBindings))) expect(() => injector(ListWrapper.concat([NeedsDirectiveFromAncestor], extraBindings)))
.toThrowError(containsRegexp( .toThrowError(containsRegexp(
`No provider for ${stringify(SimpleDirective) }! (${stringify(NeedsDirectiveFromParent) } -> ${stringify(SimpleDirective) })`)); `No provider for ${stringify(SimpleDirective) }! (${stringify(NeedsDirectiveFromAncestor) } -> ${stringify(SimpleDirective) })`));
}); });
it("should inject null when an optional dependency cannot be resolved", () => { it("should inject null when an optional dependency cannot be resolved", () => {
@ -857,10 +820,10 @@ export function main() {
var directiveBinding = var directiveBinding =
DirectiveBinding.createFromType(SimpleDirective, new dirAnn.Component()); DirectiveBinding.createFromType(SimpleDirective, new dirAnn.Component());
var shadow = hostShadowInjectors(ListWrapper.concat([directiveBinding], extraBindings), var shadow = hostShadowInjectors(ListWrapper.concat([directiveBinding], extraBindings),
[NeedsDirective]); [NeedsDirectiveFromAncestor]);
var d = shadow.get(NeedsDirective); var d = shadow.get(NeedsDirectiveFromAncestor);
expect(d).toBeAnInstanceOf(NeedsDirective); expect(d).toBeAnInstanceOf(NeedsDirectiveFromAncestor);
expect(d.dependency).toBeAnInstanceOf(SimpleDirective); expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
}); });

View File

@ -42,7 +42,6 @@ import {
forwardRef, forwardRef,
OpaqueToken, OpaqueToken,
Inject, Inject,
Parent,
Ancestor, Ancestor,
Unbounded, Unbounded,
UnboundedMetadata UnboundedMetadata
@ -423,8 +422,8 @@ export function main() {
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new viewAnn.View({ tcb.overrideView(MyComp, new viewAnn.View({
template: template:
'<some-directive><toolbar><template toolbarpart var-toolbar-prop="toolbarProp">{{ctxProp}},{{toolbarProp}},<cmp-with-parent></cmp-with-parent></template></toolbar></some-directive>', '<some-directive><toolbar><template toolbarpart var-toolbar-prop="toolbarProp">{{ctxProp}},{{toolbarProp}},<cmp-with-ancestor></cmp-with-ancestor></template></toolbar></some-directive>',
directives: [SomeDirective, CompWithParent, ToolbarComponent, ToolbarPart] directives: [SomeDirective, CompWithAncestor, ToolbarComponent, ToolbarPart]
})) }))
.createAsync(MyComp) .createAsync(MyComp)
.then((rootTC) => { .then((rootTC) => {
@ -433,7 +432,7 @@ export function main() {
expect(rootTC.nativeElement) expect(rootTC.nativeElement)
.toHaveText( .toHaveText(
'TOOLBAR(From myComp,From toolbar,Component with an injected parent)'); 'TOOLBAR(From myComp,From toolbar,Component with an injected ancestor)');
async.done(); async.done();
}); });
@ -663,25 +662,6 @@ export function main() {
})})); })}));
}); });
it('should create a component that injects a @Parent',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new viewAnn.View({
template:
'<some-directive><cmp-with-parent #child></cmp-with-parent></some-directive>',
directives: [SomeDirective, CompWithParent]
}))
.createAsync(MyComp)
.then((rootTC) => {
var childComponent = rootTC.componentViewChildren[0].getLocal('child');
expect(childComponent.myParent).toBeAnInstanceOf(SomeDirective);
async.done();
})}));
it('should create a component that injects an @Ancestor', it('should create a component that injects an @Ancestor',
inject([TestComponentBuilder, AsyncTestCompleter], inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => { (tcb: TestComponentBuilder, async) => {
@ -1497,14 +1477,6 @@ class SomeDirective {
class SomeDirectiveMissingAnnotation {} class SomeDirectiveMissingAnnotation {}
@Component({selector: 'cmp-with-parent'})
@View({template: '<p>Component with an injected parent</p>', directives: [SomeDirective]})
@Injectable()
class CompWithParent {
myParent: SomeDirective;
constructor(@Parent() someComp: SomeDirective) { this.myParent = someComp; }
}
@Component({selector: 'cmp-with-ancestor'}) @Component({selector: 'cmp-with-ancestor'})
@View({template: '<p>Component with an injected ancestor</p>', directives: [SomeDirective]}) @View({template: '<p>Component with an injected ancestor</p>', directives: [SomeDirective]})
@Injectable() @Injectable()
@ -1673,7 +1645,7 @@ class PrivateImpl extends PublicApi {
@Directive({selector: '[needs-public-api]'}) @Directive({selector: '[needs-public-api]'})
@Injectable() @Injectable()
class NeedsPublicApi { class NeedsPublicApi {
constructor(@Parent() api: PublicApi) { expect(api instanceof PrivateImpl).toBe(true); } constructor(@Ancestor() api: PublicApi) { expect(api instanceof PrivateImpl).toBe(true); }
} }
@Directive({selector: '[toolbarpart]'}) @Directive({selector: '[toolbarpart]'})

View File

@ -10,19 +10,27 @@ import {
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import { import {
Injector, Injector,
ProtoInjector,
bind, bind,
ResolvedBinding, ResolvedBinding,
Key, Key,
forwardRef, forwardRef,
DependencyMetadata, DependencyMetadata,
Injectable, Injectable,
InjectMetadata InjectMetadata,
SelfMetadata,
AncestorMetadata,
UnboundedMetadata,
Optional,
Inject,
BindingWithVisibility,
PUBLIC,
PRIVATE,
PUBLIC_AND_PRIVATE
} from 'angular2/di'; } from 'angular2/di';
import {InjectorInlineStrategy, InjectorDynamicStrategy} from 'angular2/src/di/injector'; import {InjectorInlineStrategy, InjectorDynamicStrategy} from 'angular2/src/di/injector';
import {Optional, Inject} from 'angular2/src/di/decorators';
class CustomDependencyMetadata extends DependencyMetadata {} class CustomDependencyMetadata extends DependencyMetadata {}
class Engine {} class Engine {}
@ -362,13 +370,144 @@ export function main() {
expect(engineFromChild).toBeAnInstanceOf(TurboEngine); expect(engineFromChild).toBeAnInstanceOf(TurboEngine);
}); });
it("should give access to direct parent", () => { it("should give access to parent", () => {
var parent = Injector.resolveAndCreate([]); var parent = Injector.resolveAndCreate([]);
var child = parent.resolveAndCreateChild([]); var child = parent.resolveAndCreateChild([]);
expect(child.parent).toBe(parent); expect(child.parent).toBe(parent);
}); });
}); });
describe("depedency resolution", () => {
describe("@Self()", () => {
it("should return a dependency from self", () => {
var inj = Injector.resolveAndCreate(
[Engine, bind(Car).toFactory((e) => new Car(e), [[Engine, new SelfMetadata()]])]);
expect(inj.get(Car)).toBeAnInstanceOf(Car);
});
it("should throw when not requested binding on self", () => {
var parent = Injector.resolveAndCreate([Engine]);
var child = parent.resolveAndCreateChild(
[bind(Car).toFactory((e) => new Car(e), [[Engine, new SelfMetadata()]])]);
expect(() => child.get(Car))
.toThrowError(`No provider for Engine! (${stringify(Car)} -> ${stringify(Engine)})`);
});
});
describe("@Ancestor()", () => {
it("should return a dependency from same boundary", () => {
var parent = Injector.resolveAndCreate([Engine]);
var child = parent.resolveAndCreateChild(
[bind(Car).toFactory((e) => new Car(e), [[Engine, new AncestorMetadata()]])]);
expect(child.get(Car)).toBeAnInstanceOf(Car);
});
it("should return a private dependency declared at the boundary", () => {
var engine = Injector.resolve([Engine])[0];
var protoParent = new ProtoInjector([new BindingWithVisibility(engine, PRIVATE)]);
var parent = new Injector(protoParent);
var child = Injector.resolveAndCreate(
[bind(Car).toFactory((e) => new Car(e), [[Engine, new AncestorMetadata()]])]);
child.internalStrategy.attach(parent, true); // boundary
expect(child.get(Car)).toBeAnInstanceOf(Car);
});
it("should not return a public dependency declared at the boundary", () => {
var engine = Injector.resolve([Engine])[0];
var protoParent = new ProtoInjector([new BindingWithVisibility(engine, PUBLIC)]);
var parent = new Injector(protoParent);
var child = Injector.resolveAndCreate(
[bind(Car).toFactory((e) => new Car(e), [[Engine, new AncestorMetadata()]])]);
child.internalStrategy.attach(parent, true); // boundary
expect(() => child.get(Car))
.toThrowError(`No provider for Engine! (${stringify(Car)} -> ${stringify(Engine)})`);
});
it("should return a dependency from self when explicitly specified", () => {
var parent = Injector.resolveAndCreate([Engine]);
var child = parent.resolveAndCreateChild([
bind(Engine)
.toClass(TurboEngine),
bind(Car)
.toFactory((e) => new Car(e), [[Engine, new AncestorMetadata({self: true})]])
]);
expect(child.get(Car).engine).toBeAnInstanceOf(TurboEngine);
});
});
describe("@Unboudned()", () => {
it("should return a private dependency declared at the boundary", () => {
var engine = Injector.resolve([Engine])[0];
var protoParent = new ProtoInjector([new BindingWithVisibility(engine, PRIVATE)]);
var parent = new Injector(protoParent);
var child = Injector.resolveAndCreate([
bind(Engine)
.toClass(BrokenEngine),
bind(Car).toFactory((e) => new Car(e), [[Engine, new UnboundedMetadata()]])
]);
child.internalStrategy.attach(parent, true); // boundary
expect(child.get(Car)).toBeAnInstanceOf(Car);
});
it("should return a public dependency declared at the boundary", () => {
var engine = Injector.resolve([Engine])[0];
var protoParent = new ProtoInjector([new BindingWithVisibility(engine, PUBLIC)]);
var parent = new Injector(protoParent);
var child = Injector.resolveAndCreate([
bind(Engine)
.toClass(BrokenEngine),
bind(Car).toFactory((e) => new Car(e), [[Engine, new UnboundedMetadata()]])
]);
child.internalStrategy.attach(parent, true); // boundary
expect(child.get(Car)).toBeAnInstanceOf(Car);
});
it("should not return a private dependency declared NOT at the boundary", () => {
var engine = Injector.resolve([Engine])[0];
var protoParent = new ProtoInjector([new BindingWithVisibility(engine, PRIVATE)]);
var parent = new Injector(protoParent);
var child = Injector.resolveAndCreate([
bind(Engine)
.toClass(BrokenEngine),
bind(Car).toFactory((e) => new Car(e), [[Engine, new UnboundedMetadata()]])
]);
child.internalStrategy.attach(parent, false);
expect(() => child.get(Car))
.toThrowError(`No provider for Engine! (${stringify(Car)} -> ${stringify(Engine)})`);
});
it("should return a dependency from self when explicitly specified", () => {
var parent = Injector.resolveAndCreate([Engine]);
var child = parent.resolveAndCreateChild([
bind(Engine)
.toClass(TurboEngine),
bind(Car)
.toFactory((e) => new Car(e), [[Engine, new UnboundedMetadata({self: true})]])
]);
expect(child.get(Car).engine).toBeAnInstanceOf(TurboEngine);
});
});
});
describe('resolve', () => { describe('resolve', () => {
it('should resolve and flatten', () => { it('should resolve and flatten', () => {
var bindings = Injector.resolve([Engine, [BrokenEngine]]); var bindings = Injector.resolve([Engine, [BrokenEngine]]);

View File

@ -2,7 +2,7 @@ import {
Component, Component,
Directive, Directive,
View, View,
Parent, Ancestor,
ElementRef, ElementRef,
DynamicComponentLoader, DynamicComponentLoader,
ComponentRef, ComponentRef,
@ -243,7 +243,7 @@ class MdDialogContainer {
*/ */
@Directive({selector: 'md-dialog-content'}) @Directive({selector: 'md-dialog-content'})
class MdDialogContent { class MdDialogContent {
constructor(@Parent() dialogContainer: MdDialogContainer, elementRef: ElementRef) { constructor(@Ancestor() dialogContainer: MdDialogContainer, elementRef: ElementRef) {
dialogContainer.contentRef = elementRef; dialogContainer.contentRef = elementRef;
} }
} }

View File

@ -1,4 +1,4 @@
import {Component, View, Parent, LifecycleEvent} from 'angular2/angular2'; import {Component, View, Ancestor, LifecycleEvent} from 'angular2/angular2';
import {ListWrapper} from 'angular2/src/facade/collection'; import {ListWrapper} from 'angular2/src/facade/collection';
import {StringWrapper, isPresent, isString, NumberWrapper} from 'angular2/src/facade/lang'; import {StringWrapper, isPresent, isString, NumberWrapper} from 'angular2/src/facade/lang';
@ -238,7 +238,7 @@ export class MdGridTile {
isRegisteredWithGridList: boolean; isRegisteredWithGridList: boolean;
constructor(@Parent() gridList: MdGridList) { constructor(@Ancestor() gridList: MdGridList) {
this.gridList = gridList; this.gridList = gridList;
// Tiles default to 1x1, but rowspan and colspan can be changed via binding. // Tiles default to 1x1, but rowspan and colspan can be changed via binding.

View File

@ -1,4 +1,4 @@
import {Directive, LifecycleEvent, Attribute, Parent} from 'angular2/angular2'; import {Directive, LifecycleEvent, Attribute, Ancestor} from 'angular2/angular2';
import {ObservableWrapper, EventEmitter} from 'angular2/src/facade/async'; import {ObservableWrapper, EventEmitter} from 'angular2/src/facade/async';
@ -73,7 +73,7 @@ export class MdInput {
mdChange: EventEmitter; mdChange: EventEmitter;
mdFocusChange: EventEmitter; mdFocusChange: EventEmitter;
constructor(@Attribute('value') value: string, @Parent() container: MdInputContainer, constructor(@Attribute('value') value: string, @Ancestor() container: MdInputContainer,
@Attribute('id') id: string) { @Attribute('id') id: string) {
// TODO(jelbourn): Remove this when #1402 is done. // TODO(jelbourn): Remove this when #1402 is done.
this.yes = true; this.yes = true;
@ -111,7 +111,7 @@ export class MdInput {
export class MdTextarea extends MdInput { export class MdTextarea extends MdInput {
constructor( constructor(
@Attribute('value') value: string, @Attribute('value') value: string,
@Parent() container: MdInputContainer, @Ancestor() container: MdInputContainer,
@Attribute('id') id: string) { @Attribute('id') id: string) {
super(value, container, id); super(value, container, id);
} }

View File

@ -1,12 +1,4 @@
import { import {Component, View, LifecycleEvent, Ancestor, Attribute, Optional} from 'angular2/angular2';
Component,
View,
LifecycleEvent,
Parent,
Ancestor,
Attribute,
Optional
} from 'angular2/angular2';
import {isPresent, StringWrapper, NumberWrapper} from 'angular2/src/facade/lang'; import {isPresent, StringWrapper, NumberWrapper} from 'angular2/src/facade/lang';
import {ObservableWrapper, EventEmitter} from 'angular2/src/facade/async'; import {ObservableWrapper, EventEmitter} from 'angular2/src/facade/async';
@ -225,7 +217,7 @@ export class MdRadioButton {
role: string; role: string;
constructor(@Optional() @Parent() radioGroup: MdRadioGroup, @Attribute('id') id: string, constructor(@Optional() @Ancestor() radioGroup: MdRadioGroup, @Attribute('id') id: string,
@Attribute('tabindex') tabindex: string, radioDispatcher: MdRadioDispatcher) { @Attribute('tabindex') tabindex: string, radioDispatcher: MdRadioDispatcher) {
// Assertions. Ideally these should be stripped out by the compiler. // Assertions. Ideally these should be stripped out by the compiler.
// TODO(jelbourn): Assert that there's no name binding AND a parent radio group. // TODO(jelbourn): Assert that there's no name binding AND a parent radio group.