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

@ -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", () => {

View File

@ -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:
'<some-directive><toolbar><template toolbarpart var-toolbar-prop="toolbarProp">{{ctxProp}},{{toolbarProp}},<cmp-with-parent></cmp-with-parent></template></toolbar></some-directive>',
directives: [SomeDirective, CompWithParent, ToolbarComponent, ToolbarPart]
'<some-directive><toolbar><template toolbarpart var-toolbar-prop="toolbarProp">{{ctxProp}},{{toolbarProp}},<cmp-with-ancestor></cmp-with-ancestor></template></toolbar></some-directive>',
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:
'<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',
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
@ -1497,14 +1477,6 @@ class SomeDirective {
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'})
@View({template: '<p>Component with an injected ancestor</p>', 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]'})

View File

@ -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]]);