From 6f4a39c337f85a63d379da349855814a623ce28e Mon Sep 17 00:00:00 2001 From: vsavkin Date: Mon, 13 Jul 2015 15:57:06 -0700 Subject: [PATCH] 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. --- modules/angular2/di.ts | 3 +- modules/angular2/docs/core/02_directives.md | 7 +- .../src/core/annotations_impl/annotations.ts | 27 +--- .../src/core/compiler/element_injector.ts | 2 +- modules/angular2/src/di/decorators.dart | 7 - modules/angular2/src/di/decorators.ts | 14 -- modules/angular2/src/di/injector.ts | 104 ++++++++----- modules/angular2/src/di/metadata.ts | 57 ++----- modules/angular2/src/directives/ng_switch.ts | 6 +- .../core/compiler/element_injector_spec.ts | 89 ++++------- .../test/core/compiler/integration_spec.ts | 36 +---- modules/angular2/test/di/injector_spec.ts | 147 +++++++++++++++++- .../src/components/dialog/dialog.ts | 4 +- .../src/components/grid_list/grid_list.ts | 4 +- .../src/components/input/input.ts | 6 +- .../src/components/radio/radio_button.ts | 12 +- 16 files changed, 269 insertions(+), 256 deletions(-) diff --git a/modules/angular2/di.ts b/modules/angular2/di.ts index cc8220464e..c2a90d005d 100644 --- a/modules/angular2/di.ts +++ b/modules/angular2/di.ts @@ -10,7 +10,6 @@ export { InjectableMetadata, VisibilityMetadata, SelfMetadata, - ParentMetadata, AncestorMetadata, UnboundedMetadata, DependencyMetadata, @@ -24,12 +23,12 @@ export {forwardRef, resolveForwardRef, ForwardRefFn} from './src/di/forward_ref' export { Injector, ProtoInjector, + BindingWithVisibility, DependencyProvider, PUBLIC_AND_PRIVATE, PUBLIC, PRIVATE, undefinedValue - } from './src/di/injector'; export {Binding, BindingBuilder, ResolvedBinding, Dependency, bind} from './src/di/binding'; export {Key, KeyRegistry, TypeLiteral} from './src/di/key'; diff --git a/modules/angular2/docs/core/02_directives.md b/modules/angular2/docs/core/02_directives.md index f72df4e61d..6073dcdc50 100644 --- a/modules/angular2/docs/core/02_directives.md +++ b/modules/angular2/docs/core/02_directives.md @@ -262,7 +262,6 @@ There are five kinds of visibilities: * (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. -* `@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`) * `@descendant`: Inject a list of any children which match a given type. (Used with `Query`) @@ -301,7 +300,7 @@ class FieldSet { | class Field { | constructor( | @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 { constructor( - @Parent() dad:Dad, + @Ancestor() dad:Dad, @Optional() grandpa:Grandpa ) { this.name = 'Billy'; @@ -354,7 +353,7 @@ class Kid { directives: [Kid] }) class Dad { - constructor(@Parent() dad:Grandpa) { + constructor(@Ancestor() dad:Grandpa) { this.name = 'Joe Jr'; this.dad = dad.name; } diff --git a/modules/angular2/src/core/annotations_impl/annotations.ts b/modules/angular2/src/core/annotations_impl/annotations.ts index 1a96cbf085..b423cefe60 100644 --- a/modules/angular2/src/core/annotations_impl/annotations.ts +++ b/modules/angular2/src/core/annotations_impl/annotations.ts @@ -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 * resolve it, it will * be ignored. - * - `@Parent() directive:DirectiveType`: any directive that matches the type on a direct parent - * element only. * - `@Query(DirectiveType) query:QueryList`: A live collection of direct child * directives. * - `@QueryDescendants(DirectiveType) query:QueryList`: 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 * `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 * * 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 - * parents recursively. If `dependency="2"` didn't exist on the direct parent, this injection would + * `@Ancestor` checks the parent, as well as its parents recursively. If `dependency="2"` didn't + * exist on the direct parent, this injection would * have returned * `dependency="1"`. * diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index 8b9451be71..4099a2fb18 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -397,7 +397,7 @@ export class ProtoElementInjector { public directiveVariableBindings: Map) { var length = bwv.length; - this.protoInjector = new ProtoInjector(bwv, distanceToParent); + this.protoInjector = new ProtoInjector(bwv); this.eventEmitterAccessors = ListWrapper.createFixedSize(length); this.hostActionAccessors = ListWrapper.createFixedSize(length); diff --git a/modules/angular2/src/di/decorators.dart b/modules/angular2/src/di/decorators.dart index 7134628a07..9736d9db66 100644 --- a/modules/angular2/src/di/decorators.dart +++ b/modules/angular2/src/di/decorators.dart @@ -31,13 +31,6 @@ class Self extends SelfMetadata { const Self(): super(); } -/** - * {@link ParentMetadata}. - */ -class Parent extends ParentMetadata { - const Parent({bool self}): super(self:self); -} - /** * {@link AncestorMetadata}. */ diff --git a/modules/angular2/src/di/decorators.ts b/modules/angular2/src/di/decorators.ts index 3d138c7721..95d62093bc 100644 --- a/modules/angular2/src/di/decorators.ts +++ b/modules/angular2/src/di/decorators.ts @@ -4,7 +4,6 @@ import { InjectableMetadata, SelfMetadata, VisibilityMetadata, - ParentMetadata, AncestorMetadata, UnboundedMetadata } from './metadata'; @@ -42,14 +41,6 @@ export interface SelfFactory { new (): SelfMetadata; } -/** - * Factory for creating {@link ParentMetadata}. - */ -export interface ParentFactory { - (visibility?: {self: boolean}): any; - new (visibility?: {self: boolean}): ParentMetadata; -} - /** * Factory for creating {@link AncestorMetadata}. */ @@ -86,11 +77,6 @@ export var Injectable: InjectableFactory = makeDecorator(Inje */ export var Self: SelfFactory = makeParamDecorator(SelfMetadata); -/** - * Factory for creating {@link ParentMetadata}. - */ -export var Parent: ParentFactory = makeParamDecorator(ParentMetadata); - /** * Factory for creating {@link AncestorMetadata}. */ diff --git a/modules/angular2/src/di/injector.ts b/modules/angular2/src/di/injector.ts index d06980f34e..cd185c78e7 100644 --- a/modules/angular2/src/di/injector.ts +++ b/modules/angular2/src/di/injector.ts @@ -14,7 +14,7 @@ import { import {FunctionWrapper, Type, isPresent, isBlank, CONST_EXPR} from 'angular2/src/facade/lang'; import {Key} from './key'; 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 _notFound = CONST_EXPR(new Object()); @@ -175,7 +175,7 @@ export class ProtoInjectorDynamicStrategy implements ProtoInjectorStrategy { export class ProtoInjector { _strategy: ProtoInjectorStrategy; - constructor(bwv: BindingWithVisibility[], public distanceToParent: number) { + constructor(bwv: BindingWithVisibility[]) { this._strategy = bwv.length > _MAX_CONSTRUCTION_COUNTER ? new ProtoInjectorDynamicStrategy(this, bwv) : new ProtoInjectorInlineStrategy(this, bwv); @@ -459,7 +459,7 @@ export class Injector { static fromResolvedBindings(bindings: List, depProvider: DependencyProvider = null): Injector { 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); return inj; } @@ -542,7 +542,7 @@ export class Injector { createChildFromResolved(bindings: List, depProvider: DependencyProvider = null): Injector { 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); inj._parent = this; return inj; @@ -678,49 +678,79 @@ export class Injector { return this; } - var inj = this; - var lastInjector = false; - var depth = depVisibility.depth; + if (depVisibility instanceof SelfMetadata) { + return this._getByKeySelf(key, optional, bindingVisibility); - if (!depVisibility.includeSelf) { - depth -= inj._proto.distanceToParent; + } else if (depVisibility instanceof AncestorMetadata) { + return this._getByKeyAncestor(key, optional, bindingVisibility, depVisibility.includeSelf); - if (inj._isBoundary) { - if (depVisibility.crossBoundaries) { - bindingVisibility = PUBLIC_AND_PRIVATE; - } else { - bindingVisibility = PRIVATE; - 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; + } else { + return this._getByKeyUnbounded(key, optional, bindingVisibility, depVisibility.includeSelf); } + } + _throwOrNull(key: Key, optional: boolean): any { if (optional) { return null; } else { 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); diff --git a/modules/angular2/src/di/metadata.ts b/modules/angular2/src/di/metadata.ts index 0fd6da2bae..ee3bfffc38 100644 --- a/modules/angular2/src/di/metadata.ts +++ b/modules/angular2/src/di/metadata.ts @@ -42,21 +42,21 @@ export class OptionalMetadata { * For example: * * ``` - * class Parent extends DependencyMetadata {} + * class Exclude extends DependencyMetadata {} * class NotDependencyProperty {} * * class AComponent { - * constructor(@Parent @NotDependencyProperty aService:AService) {} + * constructor(@Exclude @NotDependencyProperty aService:AService) {} * } * ``` * * 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. */ @CONST() @@ -85,17 +85,16 @@ export class InjectableMetadata { /** * Specifies how injector should resolve a dependency. * - * See {@link Self}, {@link Parent}, {@link Ancestor}, {@link Unbounded}. + * See {@link Self}, {@link Ancestor}, {@link Unbounded}. */ @CONST() 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; } 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() export class SelfMetadata extends VisibilityMetadata { - constructor() { super(0, false, true); } + constructor() { super(false, true); } 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. * @@ -192,7 +155,7 @@ export class ParentMetadata extends VisibilityMetadata { */ @CONST() 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}})`; } } @@ -229,7 +192,7 @@ export class AncestorMetadata extends VisibilityMetadata { */ @CONST() 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}})`; } } diff --git a/modules/angular2/src/directives/ng_switch.ts b/modules/angular2/src/directives/ng_switch.ts index 556069801d..c163969eda 100644 --- a/modules/angular2/src/directives/ng_switch.ts +++ b/modules/angular2/src/directives/ng_switch.ts @@ -1,5 +1,5 @@ import {Directive} from 'angular2/annotations'; -import {Parent} from 'angular2/di'; +import {Ancestor} from 'angular2/di'; import {ViewContainerRef, TemplateRef} from 'angular2/core'; import {isPresent, isBlank, normalizeBlank} from 'angular2/src/facade/lang'; import {ListWrapper, List, MapWrapper, Map} from 'angular2/src/facade/collection'; @@ -157,7 +157,7 @@ export class NgSwitchWhen { _view: SwitchView; constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef, - @Parent() sswitch: NgSwitch) { + @Ancestor() sswitch: NgSwitch) { // `_whenDefault` is used as a marker for a not yet initialized value this._value = _whenDefault; this._switch = sswitch; @@ -187,7 +187,7 @@ export class NgSwitchWhen { @Directive({selector: '[ng-switch-default]'}) export class NgSwitchDefault { constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef, - @Parent() sswitch: NgSwitch) { + @Ancestor() sswitch: NgSwitch) { sswitch._registerView(_whenDefault, new SwitchView(viewContainer, templateRef)); } } diff --git a/modules/angular2/test/core/compiler/element_injector_spec.ts b/modules/angular2/test/core/compiler/element_injector_spec.ts index ef06b3c315..581bab1b6a 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.ts +++ b/modules/angular2/test/core/compiler/element_injector_spec.ts @@ -40,7 +40,7 @@ import { Directive, LifecycleEvent } 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 {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref'; import {TemplateRef} from 'angular2/src/core/compiler/template_ref'; @@ -102,18 +102,6 @@ class OptionallyNeedsDirective { 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() class NeedsDirectiveFromAncestor { dependency: SimpleDirective; @@ -609,7 +597,7 @@ export function main() { bind('injectable2') .toFactory( (val) => `${val}-injectable2`, - [[new InjectMetadata('injectable1'), new ParentMetadata()]]) + [[new InjectMetadata('injectable1'), new AncestorMetadata()]]) ] }))]); expect(childInj.get('injectable2')).toEqual('injectable1-injectable2'); @@ -775,31 +763,6 @@ export function main() { 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", () => { var child = parentChildInjectors(ListWrapper.concat([SimpleDirective], extraBindings), [NeedsDirectiveFromAncestor]); @@ -822,9 +785,9 @@ export function main() { }); it("should throw when a dependency cannot be resolved", () => { - expect(() => injector(ListWrapper.concat([NeedsDirectiveFromParent], extraBindings))) + expect(() => injector(ListWrapper.concat([NeedsDirectiveFromAncestor], extraBindings))) .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", () => { @@ -853,30 +816,30 @@ export function main() { .toThrowError(`Index ${firsIndexOut} is out-of-bounds.`); }); - it("should instantiate directives that depend on the containing component", () => { - var directiveBinding = - DirectiveBinding.createFromType(SimpleDirective, new dirAnn.Component()); - var shadow = hostShadowInjectors(ListWrapper.concat([directiveBinding], extraBindings), - [NeedsDirective]); + it("should instantiate directives that depend on the containing component", () => { + var directiveBinding = + DirectiveBinding.createFromType(SimpleDirective, new dirAnn.Component()); + var shadow = hostShadowInjectors(ListWrapper.concat([directiveBinding], extraBindings), + [NeedsDirectiveFromAncestor]); - var d = shadow.get(NeedsDirective); - expect(d).toBeAnInstanceOf(NeedsDirective); - expect(d.dependency).toBeAnInstanceOf(SimpleDirective); - }); + var d = shadow.get(NeedsDirectiveFromAncestor); + expect(d).toBeAnInstanceOf(NeedsDirectiveFromAncestor); + expect(d.dependency).toBeAnInstanceOf(SimpleDirective); + }); - it("should not instantiate directives that depend on other directives in the containing component's ElementInjector", - () => { - var directiveBinding = - DirectiveBinding.createFromType(SomeOtherDirective, new dirAnn.Component()); - expect(() => - { - hostShadowInjectors( - ListWrapper.concat([directiveBinding, SimpleDirective], extraBindings), - [NeedsDirective]); - }) - .toThrowError(containsRegexp( - `No provider for ${stringify(SimpleDirective) }! (${stringify(NeedsDirective) } -> ${stringify(SimpleDirective) })`)); - }); + it("should not instantiate directives that depend on other directives in the containing component's ElementInjector", + () => { + var directiveBinding = + DirectiveBinding.createFromType(SomeOtherDirective, new dirAnn.Component()); + expect(() => + { + hostShadowInjectors( + ListWrapper.concat([directiveBinding, SimpleDirective], extraBindings), + [NeedsDirective]); + }) + .toThrowError(containsRegexp( + `No provider for ${stringify(SimpleDirective) }! (${stringify(NeedsDirective) } -> ${stringify(SimpleDirective) })`)); + }); }); describe("lifecycle", () => { diff --git a/modules/angular2/test/core/compiler/integration_spec.ts b/modules/angular2/test/core/compiler/integration_spec.ts index 5c413bae7c..4defa06921 100644 --- a/modules/angular2/test/core/compiler/integration_spec.ts +++ b/modules/angular2/test/core/compiler/integration_spec.ts @@ -42,7 +42,6 @@ import { forwardRef, OpaqueToken, Inject, - Parent, Ancestor, Unbounded, UnboundedMetadata @@ -423,8 +422,8 @@ export function main() { inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { tcb.overrideView(MyComp, new viewAnn.View({ template: - '', - directives: [SomeDirective, CompWithParent, ToolbarComponent, ToolbarPart] + '', + directives: [SomeDirective, CompWithAncestor, ToolbarComponent, ToolbarPart] })) .createAsync(MyComp) .then((rootTC) => { @@ -433,7 +432,7 @@ export function main() { expect(rootTC.nativeElement) .toHaveText( - 'TOOLBAR(From myComp,From toolbar,Component with an injected parent)'); + 'TOOLBAR(From myComp,From toolbar,Component with an injected ancestor)'); 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: - '', - 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', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { @@ -1497,14 +1477,6 @@ class SomeDirective { class SomeDirectiveMissingAnnotation {} -@Component({selector: 'cmp-with-parent'}) -@View({template: '

Component with an injected parent

', directives: [SomeDirective]}) -@Injectable() -class CompWithParent { - myParent: SomeDirective; - constructor(@Parent() someComp: SomeDirective) { this.myParent = someComp; } -} - @Component({selector: 'cmp-with-ancestor'}) @View({template: '

Component with an injected ancestor

', directives: [SomeDirective]}) @Injectable() @@ -1673,7 +1645,7 @@ class PrivateImpl extends PublicApi { @Directive({selector: '[needs-public-api]'}) @Injectable() 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]'}) diff --git a/modules/angular2/test/di/injector_spec.ts b/modules/angular2/test/di/injector_spec.ts index 84ba755a6d..491a3d671e 100644 --- a/modules/angular2/test/di/injector_spec.ts +++ b/modules/angular2/test/di/injector_spec.ts @@ -10,19 +10,27 @@ import { } from 'angular2/test_lib'; import { Injector, + ProtoInjector, bind, ResolvedBinding, Key, forwardRef, DependencyMetadata, Injectable, - InjectMetadata + InjectMetadata, + SelfMetadata, + AncestorMetadata, + UnboundedMetadata, + Optional, + Inject, + BindingWithVisibility, + PUBLIC, + PRIVATE, + PUBLIC_AND_PRIVATE } from 'angular2/di'; import {InjectorInlineStrategy, InjectorDynamicStrategy} from 'angular2/src/di/injector'; -import {Optional, Inject} from 'angular2/src/di/decorators'; - class CustomDependencyMetadata extends DependencyMetadata {} class Engine {} @@ -362,13 +370,144 @@ export function main() { expect(engineFromChild).toBeAnInstanceOf(TurboEngine); }); - it("should give access to direct parent", () => { + it("should give access to parent", () => { var parent = Injector.resolveAndCreate([]); var child = parent.resolveAndCreateChild([]); 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', () => { it('should resolve and flatten', () => { var bindings = Injector.resolve([Engine, [BrokenEngine]]); diff --git a/modules/angular2_material/src/components/dialog/dialog.ts b/modules/angular2_material/src/components/dialog/dialog.ts index 27213d5798..ae9ef5f17a 100644 --- a/modules/angular2_material/src/components/dialog/dialog.ts +++ b/modules/angular2_material/src/components/dialog/dialog.ts @@ -2,7 +2,7 @@ import { Component, Directive, View, - Parent, + Ancestor, ElementRef, DynamicComponentLoader, ComponentRef, @@ -243,7 +243,7 @@ class MdDialogContainer { */ @Directive({selector: 'md-dialog-content'}) class MdDialogContent { - constructor(@Parent() dialogContainer: MdDialogContainer, elementRef: ElementRef) { + constructor(@Ancestor() dialogContainer: MdDialogContainer, elementRef: ElementRef) { dialogContainer.contentRef = elementRef; } } diff --git a/modules/angular2_material/src/components/grid_list/grid_list.ts b/modules/angular2_material/src/components/grid_list/grid_list.ts index fd1a2bf384..5cc85cdc16 100644 --- a/modules/angular2_material/src/components/grid_list/grid_list.ts +++ b/modules/angular2_material/src/components/grid_list/grid_list.ts @@ -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 {StringWrapper, isPresent, isString, NumberWrapper} from 'angular2/src/facade/lang'; @@ -238,7 +238,7 @@ export class MdGridTile { isRegisteredWithGridList: boolean; - constructor(@Parent() gridList: MdGridList) { + constructor(@Ancestor() gridList: MdGridList) { this.gridList = gridList; // Tiles default to 1x1, but rowspan and colspan can be changed via binding. diff --git a/modules/angular2_material/src/components/input/input.ts b/modules/angular2_material/src/components/input/input.ts index 638b3eff35..8f6c1d6763 100644 --- a/modules/angular2_material/src/components/input/input.ts +++ b/modules/angular2_material/src/components/input/input.ts @@ -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'; @@ -73,7 +73,7 @@ export class MdInput { mdChange: EventEmitter; mdFocusChange: EventEmitter; - constructor(@Attribute('value') value: string, @Parent() container: MdInputContainer, + constructor(@Attribute('value') value: string, @Ancestor() container: MdInputContainer, @Attribute('id') id: string) { // TODO(jelbourn): Remove this when #1402 is done. this.yes = true; @@ -111,7 +111,7 @@ export class MdInput { export class MdTextarea extends MdInput { constructor( @Attribute('value') value: string, - @Parent() container: MdInputContainer, + @Ancestor() container: MdInputContainer, @Attribute('id') id: string) { super(value, container, id); } diff --git a/modules/angular2_material/src/components/radio/radio_button.ts b/modules/angular2_material/src/components/radio/radio_button.ts index d9ebb551c0..d3b31b571b 100644 --- a/modules/angular2_material/src/components/radio/radio_button.ts +++ b/modules/angular2_material/src/components/radio/radio_button.ts @@ -1,12 +1,4 @@ -import { - Component, - View, - LifecycleEvent, - Parent, - Ancestor, - Attribute, - Optional -} from 'angular2/angular2'; +import {Component, View, LifecycleEvent, Ancestor, Attribute, Optional} from 'angular2/angular2'; import {isPresent, StringWrapper, NumberWrapper} from 'angular2/src/facade/lang'; import {ObservableWrapper, EventEmitter} from 'angular2/src/facade/async'; @@ -225,7 +217,7 @@ export class MdRadioButton { 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) { // Assertions. Ideally these should be stripped out by the compiler. // TODO(jelbourn): Assert that there's no name binding AND a parent radio group.