diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index 47a8da9a9c..e63138c508 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -456,7 +456,7 @@ export class ElementInjector extends TreeNode implements Depend this._host = null; this._preBuiltObjects = null; this._strategy.callOnDestroy(); - this._injector.internalStrategy.dehydrate(); + this._strategy.dehydrate(); } onAllChangesDone(): void { @@ -476,7 +476,8 @@ export class ElementInjector extends TreeNode implements Depend this._host = host; this._preBuiltObjects = preBuiltObjects; - this._hydrateInjector(imperativelyCreatedInjector, host); + this._reattachInjectors(imperativelyCreatedInjector, host); + this._strategy.hydrate(); if (isPresent(host)) { this._addViewQueries(host); @@ -488,7 +489,7 @@ export class ElementInjector extends TreeNode implements Depend this.hydrated = true; } - private _hydrateInjector(imperativelyCreatedInjector: Injector, host: ElementInjector): void { + private _reattachInjectors(imperativelyCreatedInjector: Injector, host: ElementInjector): void { if (isPresent(this._parent)) { this._reattachInjector(this._injector, this._parent._injector, false); } else { @@ -539,7 +540,6 @@ export class ElementInjector extends TreeNode implements Depend private _reattachInjector(injector: Injector, parentInjector: Injector, isBoundary: boolean) { injector.internalStrategy.attach(parentInjector, isBoundary); - injector.internalStrategy.hydrate(); } getPipes(): Pipes { @@ -847,6 +847,8 @@ interface _ElementInjectorStrategy { buildQueries(): void; addDirectivesMatchingQuery(q: Query, res: any[]): void; getComponentBinding(): DirectiveBinding; + hydrate(): void; + dehydrate(): void; } /** @@ -856,6 +858,48 @@ interface _ElementInjectorStrategy { class ElementInjectorInlineStrategy implements _ElementInjectorStrategy { constructor(public injectorStrategy: InjectorInlineStrategy, public _ei: ElementInjector) {} + hydrate(): void { + var i = this.injectorStrategy; + var p = i.protoStrategy; + i.resetContructionCounter(); + + if (p.binding0 instanceof DirectiveBinding && isPresent(p.keyId0) && i.obj0 === undefinedValue) + i.obj0 = i.instantiateBinding(p.binding0, p.visibility0); + if (p.binding1 instanceof DirectiveBinding && isPresent(p.keyId1) && i.obj1 === undefinedValue) + i.obj1 = i.instantiateBinding(p.binding1, p.visibility1); + if (p.binding2 instanceof DirectiveBinding && isPresent(p.keyId2) && i.obj2 === undefinedValue) + i.obj2 = i.instantiateBinding(p.binding2, p.visibility2); + if (p.binding3 instanceof DirectiveBinding && isPresent(p.keyId3) && i.obj3 === undefinedValue) + i.obj3 = i.instantiateBinding(p.binding3, p.visibility3); + if (p.binding4 instanceof DirectiveBinding && isPresent(p.keyId4) && i.obj4 === undefinedValue) + i.obj4 = i.instantiateBinding(p.binding4, p.visibility4); + if (p.binding5 instanceof DirectiveBinding && isPresent(p.keyId5) && i.obj5 === undefinedValue) + i.obj5 = i.instantiateBinding(p.binding5, p.visibility5); + if (p.binding6 instanceof DirectiveBinding && isPresent(p.keyId6) && i.obj6 === undefinedValue) + i.obj6 = i.instantiateBinding(p.binding6, p.visibility6); + if (p.binding7 instanceof DirectiveBinding && isPresent(p.keyId7) && i.obj7 === undefinedValue) + i.obj7 = i.instantiateBinding(p.binding7, p.visibility7); + if (p.binding8 instanceof DirectiveBinding && isPresent(p.keyId8) && i.obj8 === undefinedValue) + i.obj8 = i.instantiateBinding(p.binding8, p.visibility8); + if (p.binding9 instanceof DirectiveBinding && isPresent(p.keyId9) && i.obj9 === undefinedValue) + i.obj9 = i.instantiateBinding(p.binding9, p.visibility9); + } + + dehydrate() { + var i = this.injectorStrategy; + + i.obj0 = undefinedValue; + i.obj1 = undefinedValue; + i.obj2 = undefinedValue; + i.obj3 = undefinedValue; + i.obj4 = undefinedValue; + i.obj5 = undefinedValue; + i.obj6 = undefinedValue; + i.obj7 = undefinedValue; + i.obj8 = undefinedValue; + i.obj9 = undefinedValue; + } + callOnDestroy(): void { var i = this.injectorStrategy; var p = i.protoStrategy; @@ -938,16 +982,46 @@ class ElementInjectorInlineStrategy implements _ElementInjectorStrategy { var i = this.injectorStrategy; var p = i.protoStrategy; - if (isPresent(p.binding0) && p.binding0.key.token === query.selector) list.push(i.obj0); - if (isPresent(p.binding1) && p.binding1.key.token === query.selector) list.push(i.obj1); - if (isPresent(p.binding2) && p.binding2.key.token === query.selector) list.push(i.obj2); - if (isPresent(p.binding3) && p.binding3.key.token === query.selector) list.push(i.obj3); - if (isPresent(p.binding4) && p.binding4.key.token === query.selector) list.push(i.obj4); - if (isPresent(p.binding5) && p.binding5.key.token === query.selector) list.push(i.obj5); - if (isPresent(p.binding6) && p.binding6.key.token === query.selector) list.push(i.obj6); - if (isPresent(p.binding7) && p.binding7.key.token === query.selector) list.push(i.obj7); - if (isPresent(p.binding8) && p.binding8.key.token === query.selector) list.push(i.obj8); - if (isPresent(p.binding9) && p.binding9.key.token === query.selector) list.push(i.obj9); + if (isPresent(p.binding0) && p.binding0.key.token === query.selector) { + if (i.obj0 === undefinedValue) i.obj0 = i.instantiateBinding(p.binding0, p.visibility0); + list.push(i.obj0); + } + if (isPresent(p.binding1) && p.binding1.key.token === query.selector) { + if (i.obj1 === undefinedValue) i.obj1 = i.instantiateBinding(p.binding1, p.visibility1); + list.push(i.obj1); + } + if (isPresent(p.binding2) && p.binding2.key.token === query.selector) { + if (i.obj2 === undefinedValue) i.obj2 = i.instantiateBinding(p.binding2, p.visibility2); + list.push(i.obj2); + } + if (isPresent(p.binding3) && p.binding3.key.token === query.selector) { + if (i.obj3 === undefinedValue) i.obj3 = i.instantiateBinding(p.binding3, p.visibility3); + list.push(i.obj3); + } + if (isPresent(p.binding4) && p.binding4.key.token === query.selector) { + if (i.obj4 === undefinedValue) i.obj4 = i.instantiateBinding(p.binding4, p.visibility4); + list.push(i.obj4); + } + if (isPresent(p.binding5) && p.binding5.key.token === query.selector) { + if (i.obj5 === undefinedValue) i.obj5 = i.instantiateBinding(p.binding5, p.visibility5); + list.push(i.obj5); + } + if (isPresent(p.binding6) && p.binding6.key.token === query.selector) { + if (i.obj6 === undefinedValue) i.obj6 = i.instantiateBinding(p.binding6, p.visibility6); + list.push(i.obj6); + } + if (isPresent(p.binding7) && p.binding7.key.token === query.selector) { + if (i.obj7 === undefinedValue) i.obj7 = i.instantiateBinding(p.binding7, p.visibility7); + list.push(i.obj7); + } + if (isPresent(p.binding8) && p.binding8.key.token === query.selector) { + if (i.obj8 === undefinedValue) i.obj8 = i.instantiateBinding(p.binding8, p.visibility8); + list.push(i.obj8); + } + if (isPresent(p.binding9) && p.binding9.key.token === query.selector) { + if (i.obj9 === undefinedValue) i.obj9 = i.instantiateBinding(p.binding9, p.visibility9); + list.push(i.obj9); + } } getComponentBinding(): DirectiveBinding { @@ -963,6 +1037,23 @@ class ElementInjectorInlineStrategy implements _ElementInjectorStrategy { class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy { constructor(public injectorStrategy: InjectorDynamicStrategy, public _ei: ElementInjector) {} + hydrate(): void { + var inj = this.injectorStrategy; + var p = inj.protoStrategy; + + for (var i = 0; i < p.keyIds.length; i++) { + if (p.bindings[i] instanceof DirectiveBinding && isPresent(p.keyIds[i]) && + inj.objs[i] === undefinedValue) { + inj.objs[i] = inj.instantiateBinding(p.bindings[i], p.visibilities[i]); + } + } + } + + dehydrate(): void { + var inj = this.injectorStrategy; + ListWrapper.fill(inj.objs, undefinedValue); + } + callOnDestroy(): void { var ist = this.injectorStrategy; var p = ist.protoStrategy; @@ -983,7 +1074,8 @@ class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy { } buildQueries(): void { - var p = this.injectorStrategy.protoStrategy; + var inj = this.injectorStrategy; + var p = inj.protoStrategy; for (var i = 0; i < p.bindings.length; i++) { if (p.bindings[i] instanceof DirectiveBinding) { @@ -997,7 +1089,12 @@ class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy { var p = ist.protoStrategy; for (var i = 0; i < p.bindings.length; i++) { - if (p.bindings[i].key.token === query.selector) list.push(ist.objs[i]); + if (p.bindings[i].key.token === query.selector) { + if (ist.objs[i] === undefinedValue) { + ist.objs[i] = ist.instantiateBinding(p.bindings[i], p.visibilities[i]); + } + list.push(ist.objs[i]); + } } } diff --git a/modules/angular2/src/di/injector.ts b/modules/angular2/src/di/injector.ts index caa6356953..1ba101f05b 100644 --- a/modules/angular2/src/di/injector.ts +++ b/modules/angular2/src/di/injector.ts @@ -192,8 +192,8 @@ export interface InjectorStrategy { getMaxNumberOfObjects(): number; attach(parent: Injector, isBoundary: boolean): void; - hydrate(): void; - dehydrate(): void; + resetContructionCounter(): void; + instantiateBinding(binding: ResolvedBinding, visibility: number): any; } export class InjectorInlineStrategy implements InjectorStrategy { @@ -210,33 +210,10 @@ export class InjectorInlineStrategy implements InjectorStrategy { constructor(public injector: Injector, public protoStrategy: ProtoInjectorInlineStrategy) {} - hydrate(): void { - var p = this.protoStrategy; - var inj = this.injector; + resetContructionCounter(): void { this.injector._constructionCounter = 0; } - inj._constructionCounter = 0; - - - if (isPresent(p.keyId0) && this.obj0 === undefinedValue) - this.obj0 = inj._new(p.binding0, p.visibility0); - if (isPresent(p.keyId1) && this.obj1 === undefinedValue) - this.obj1 = inj._new(p.binding1, p.visibility1); - if (isPresent(p.keyId2) && this.obj2 === undefinedValue) - this.obj2 = inj._new(p.binding2, p.visibility2); - if (isPresent(p.keyId3) && this.obj3 === undefinedValue) - this.obj3 = inj._new(p.binding3, p.visibility3); - if (isPresent(p.keyId4) && this.obj4 === undefinedValue) - this.obj4 = inj._new(p.binding4, p.visibility4); - if (isPresent(p.keyId5) && this.obj5 === undefinedValue) - this.obj5 = inj._new(p.binding5, p.visibility5); - if (isPresent(p.keyId6) && this.obj6 === undefinedValue) - this.obj6 = inj._new(p.binding6, p.visibility6); - if (isPresent(p.keyId7) && this.obj7 === undefinedValue) - this.obj7 = inj._new(p.binding7, p.visibility7); - if (isPresent(p.keyId8) && this.obj8 === undefinedValue) - this.obj8 = inj._new(p.binding8, p.visibility8); - if (isPresent(p.keyId9) && this.obj9 === undefinedValue) - this.obj9 = inj._new(p.binding9, p.visibility9); + instantiateBinding(binding: ResolvedBinding, visibility: number): any { + return this.injector._new(binding, visibility); } attach(parent: Injector, isBoundary: boolean): void { @@ -245,19 +222,6 @@ export class InjectorInlineStrategy implements InjectorStrategy { inj._isBoundary = isBoundary; } - dehydrate() { - this.obj0 = undefinedValue; - this.obj1 = undefinedValue; - this.obj2 = undefinedValue; - this.obj3 = undefinedValue; - this.obj4 = undefinedValue; - this.obj5 = undefinedValue; - this.obj6 = undefinedValue; - this.obj7 = undefinedValue; - this.obj8 = undefinedValue; - this.obj9 = undefinedValue; - } - getObjByKeyId(keyId: number, visibility: number): any { var p = this.protoStrategy; var inj = this.injector; @@ -352,13 +316,10 @@ export class InjectorDynamicStrategy implements InjectorStrategy { ListWrapper.fill(this.objs, undefinedValue); } - hydrate(): void { - var p = this.protoStrategy; - for (var i = 0; i < p.keyIds.length; i++) { - if (isPresent(p.keyIds[i]) && this.objs[i] === undefinedValue) { - this.objs[i] = this.injector._new(p.bindings[i], p.visibilities[i]); - } - } + resetContructionCounter(): void { this.injector._constructionCounter = 0; } + + instantiateBinding(binding: ResolvedBinding, visibility: number): any { + return this.injector._new(binding, visibility); } attach(parent: Injector, isBoundary: boolean): void { @@ -367,8 +328,6 @@ export class InjectorDynamicStrategy implements InjectorStrategy { inj._isBoundary = isBoundary; } - dehydrate(): void { ListWrapper.fill(this.objs, undefinedValue); } - getObjByKeyId(keyId: number, visibility: number): any { var p = this.protoStrategy; diff --git a/modules/angular2/test/core/compiler/element_injector_spec.ts b/modules/angular2/test/core/compiler/element_injector_spec.ts index 53c26b2343..4b4d0047a7 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.ts +++ b/modules/angular2/test/core/compiler/element_injector_spec.ts @@ -661,17 +661,49 @@ export function main() { expect(inj.get(NeedsService).service).toEqual('service'); }); - it("should not instantiate other directives that depend on viewInjector bindings", - () => { - var directiveAnnotation = new dirAnn.Component({ - viewInjector: ListWrapper.concat([bind("service").toValue("service")], extraBindings) - }); - var componentDirective = - DirectiveBinding.createFromType(SimpleDirective, directiveAnnotation); - expect(() => { injector([componentDirective, NeedsService], null); }) - .toThrowError(containsRegexp( - `No provider for service! (${stringify(NeedsService) } -> service)`)); + it("should instantiate hostInjector injectables lazily", () => { + var created = false; + var inj = injector( + ListWrapper.concat([DirectiveBinding.createFromType(SimpleDirective, new dirAnn.Component({ + hostInjector: [bind('service').toFactory(() => created = true)] + }))], + extraBindings), + null, true); + + expect(created).toBe(false); + + inj.get('service'); + + expect(created).toBe(true); + }); + + it("should instantiate viewInjector injectables lazily", () => { + var created = false; + var inj = injector( + ListWrapper.concat([DirectiveBinding.createFromType(SimpleDirective, new dirAnn.Component({ + viewInjector: [bind('service').toFactory(() => created = true)] + }))], + extraBindings), + null, true); + + expect(created).toBe(false); + + inj.get('service'); + + expect(created).toBe(true); + }); + + it("should not instantiate other directives that depend on viewInjector bindings", + () => { + var directiveAnnotation = new dirAnn.Component({ + viewInjector: ListWrapper.concat([bind("service").toValue("service")], extraBindings) }); + var componentDirective = + DirectiveBinding.createFromType(SimpleDirective, directiveAnnotation); + expect(() => { injector([componentDirective, NeedsService], null); }) + .toThrowError(containsRegexp( + `No provider for service! (${stringify(NeedsService) } -> service)`)); + }); it("should instantiate directives that depend on hostInjector bindings of other directives", () => { var shadowInj = hostShadowInjectors( diff --git a/modules/angular2/test/core/compiler/integration_spec.ts b/modules/angular2/test/core/compiler/integration_spec.ts index 242c07f64e..52a8b599b8 100644 --- a/modules/angular2/test/core/compiler/integration_spec.ts +++ b/modules/angular2/test/core/compiler/integration_spec.ts @@ -1103,6 +1103,32 @@ export function main() { expect(parent.grandParentBus).toBe(grandParent.bus); expect(child.bus).toBe(parent.bus); + async.done(); + }); + })); + + it("should create viewInjector injectables lazily", + inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + tcb.overrideView(MyComp, new viewAnn.View({ + template: ` + + + + + `, + directives: + [DirectiveConsumingInjectable, ComponentProvidingLoggingInjectable, NgIf] + })) + .createAsync(MyComp) + .then((rootTC) => { + var providing = rootTC.componentViewChildren[0].getLocal("providing"); + expect(providing.created).toBe(false); + + rootTC.componentInstance.ctxBoolProp = true; + rootTC.detectChanges(); + + expect(providing.created).toBe(true); + async.done(); }); })); @@ -1701,6 +1727,23 @@ class DirectiveWithTwoWayBinding { class InjectableService { } +function createInjectableWithLogging(inj: Injector) { + inj.get(ComponentProvidingLoggingInjectable).created = true; + return new InjectableService(); +} + +@Component({ + selector: 'component-providing-logging-injectable', + hostInjector: + [new Binding(InjectableService, {toFactory: createInjectableWithLogging, deps: [Injector]})] +}) +@View({template: ''}) +@Injectable() +class ComponentProvidingLoggingInjectable { + created: boolean = false; +} + + @Directive({selector: 'directive-providing-injectable', hostInjector: [[InjectableService]]}) @Injectable() class DirectiveProvidingInjectable {