fix(di): instatiate services lazily

This commit is contained in:
vsavkin 2015-07-13 16:28:44 -07:00
parent 2bc1217409
commit 7531b48d02
4 changed files with 207 additions and 76 deletions

View File

@ -456,7 +456,7 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
this._host = null; this._host = null;
this._preBuiltObjects = null; this._preBuiltObjects = null;
this._strategy.callOnDestroy(); this._strategy.callOnDestroy();
this._injector.internalStrategy.dehydrate(); this._strategy.dehydrate();
} }
onAllChangesDone(): void { onAllChangesDone(): void {
@ -476,7 +476,8 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
this._host = host; this._host = host;
this._preBuiltObjects = preBuiltObjects; this._preBuiltObjects = preBuiltObjects;
this._hydrateInjector(imperativelyCreatedInjector, host); this._reattachInjectors(imperativelyCreatedInjector, host);
this._strategy.hydrate();
if (isPresent(host)) { if (isPresent(host)) {
this._addViewQueries(host); this._addViewQueries(host);
@ -488,7 +489,7 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
this.hydrated = true; this.hydrated = true;
} }
private _hydrateInjector(imperativelyCreatedInjector: Injector, host: ElementInjector): void { private _reattachInjectors(imperativelyCreatedInjector: Injector, host: ElementInjector): void {
if (isPresent(this._parent)) { if (isPresent(this._parent)) {
this._reattachInjector(this._injector, this._parent._injector, false); this._reattachInjector(this._injector, this._parent._injector, false);
} else { } else {
@ -539,7 +540,6 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
private _reattachInjector(injector: Injector, parentInjector: Injector, isBoundary: boolean) { private _reattachInjector(injector: Injector, parentInjector: Injector, isBoundary: boolean) {
injector.internalStrategy.attach(parentInjector, isBoundary); injector.internalStrategy.attach(parentInjector, isBoundary);
injector.internalStrategy.hydrate();
} }
getPipes(): Pipes { getPipes(): Pipes {
@ -847,6 +847,8 @@ interface _ElementInjectorStrategy {
buildQueries(): void; buildQueries(): void;
addDirectivesMatchingQuery(q: Query, res: any[]): void; addDirectivesMatchingQuery(q: Query, res: any[]): void;
getComponentBinding(): DirectiveBinding; getComponentBinding(): DirectiveBinding;
hydrate(): void;
dehydrate(): void;
} }
/** /**
@ -856,6 +858,48 @@ interface _ElementInjectorStrategy {
class ElementInjectorInlineStrategy implements _ElementInjectorStrategy { class ElementInjectorInlineStrategy implements _ElementInjectorStrategy {
constructor(public injectorStrategy: InjectorInlineStrategy, public _ei: ElementInjector) {} 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 { callOnDestroy(): void {
var i = this.injectorStrategy; var i = this.injectorStrategy;
var p = i.protoStrategy; var p = i.protoStrategy;
@ -938,16 +982,46 @@ class ElementInjectorInlineStrategy implements _ElementInjectorStrategy {
var i = this.injectorStrategy; var i = this.injectorStrategy;
var p = i.protoStrategy; var p = i.protoStrategy;
if (isPresent(p.binding0) && p.binding0.key.token === query.selector) list.push(i.obj0); if (isPresent(p.binding0) && p.binding0.key.token === query.selector) {
if (isPresent(p.binding1) && p.binding1.key.token === query.selector) list.push(i.obj1); if (i.obj0 === undefinedValue) i.obj0 = i.instantiateBinding(p.binding0, p.visibility0);
if (isPresent(p.binding2) && p.binding2.key.token === query.selector) list.push(i.obj2); list.push(i.obj0);
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.binding1) && p.binding1.key.token === query.selector) {
if (isPresent(p.binding5) && p.binding5.key.token === query.selector) list.push(i.obj5); if (i.obj1 === undefinedValue) i.obj1 = i.instantiateBinding(p.binding1, p.visibility1);
if (isPresent(p.binding6) && p.binding6.key.token === query.selector) list.push(i.obj6); list.push(i.obj1);
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.binding2) && p.binding2.key.token === query.selector) {
if (isPresent(p.binding9) && p.binding9.key.token === query.selector) list.push(i.obj9); 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 { getComponentBinding(): DirectiveBinding {
@ -963,6 +1037,23 @@ class ElementInjectorInlineStrategy implements _ElementInjectorStrategy {
class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy { class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy {
constructor(public injectorStrategy: InjectorDynamicStrategy, public _ei: ElementInjector) {} 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 { callOnDestroy(): void {
var ist = this.injectorStrategy; var ist = this.injectorStrategy;
var p = ist.protoStrategy; var p = ist.protoStrategy;
@ -983,7 +1074,8 @@ class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy {
} }
buildQueries(): void { buildQueries(): void {
var p = this.injectorStrategy.protoStrategy; var inj = this.injectorStrategy;
var p = inj.protoStrategy;
for (var i = 0; i < p.bindings.length; i++) { for (var i = 0; i < p.bindings.length; i++) {
if (p.bindings[i] instanceof DirectiveBinding) { if (p.bindings[i] instanceof DirectiveBinding) {
@ -997,7 +1089,12 @@ class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy {
var p = ist.protoStrategy; var p = ist.protoStrategy;
for (var i = 0; i < p.bindings.length; i++) { 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]);
}
} }
} }

View File

@ -192,8 +192,8 @@ export interface InjectorStrategy {
getMaxNumberOfObjects(): number; getMaxNumberOfObjects(): number;
attach(parent: Injector, isBoundary: boolean): void; attach(parent: Injector, isBoundary: boolean): void;
hydrate(): void; resetContructionCounter(): void;
dehydrate(): void; instantiateBinding(binding: ResolvedBinding, visibility: number): any;
} }
export class InjectorInlineStrategy implements InjectorStrategy { export class InjectorInlineStrategy implements InjectorStrategy {
@ -210,33 +210,10 @@ export class InjectorInlineStrategy implements InjectorStrategy {
constructor(public injector: Injector, public protoStrategy: ProtoInjectorInlineStrategy) {} constructor(public injector: Injector, public protoStrategy: ProtoInjectorInlineStrategy) {}
hydrate(): void { resetContructionCounter(): void { this.injector._constructionCounter = 0; }
var p = this.protoStrategy;
var inj = this.injector;
inj._constructionCounter = 0; instantiateBinding(binding: ResolvedBinding, visibility: number): any {
return this.injector._new(binding, visibility);
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);
} }
attach(parent: Injector, isBoundary: boolean): void { attach(parent: Injector, isBoundary: boolean): void {
@ -245,19 +222,6 @@ export class InjectorInlineStrategy implements InjectorStrategy {
inj._isBoundary = isBoundary; 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 { getObjByKeyId(keyId: number, visibility: number): any {
var p = this.protoStrategy; var p = this.protoStrategy;
var inj = this.injector; var inj = this.injector;
@ -352,13 +316,10 @@ export class InjectorDynamicStrategy implements InjectorStrategy {
ListWrapper.fill(this.objs, undefinedValue); ListWrapper.fill(this.objs, undefinedValue);
} }
hydrate(): void { resetContructionCounter(): void { this.injector._constructionCounter = 0; }
var p = this.protoStrategy;
for (var i = 0; i < p.keyIds.length; i++) { instantiateBinding(binding: ResolvedBinding, visibility: number): any {
if (isPresent(p.keyIds[i]) && this.objs[i] === undefinedValue) { return this.injector._new(binding, visibility);
this.objs[i] = this.injector._new(p.bindings[i], p.visibilities[i]);
}
}
} }
attach(parent: Injector, isBoundary: boolean): void { attach(parent: Injector, isBoundary: boolean): void {
@ -367,8 +328,6 @@ export class InjectorDynamicStrategy implements InjectorStrategy {
inj._isBoundary = isBoundary; inj._isBoundary = isBoundary;
} }
dehydrate(): void { ListWrapper.fill(this.objs, undefinedValue); }
getObjByKeyId(keyId: number, visibility: number): any { getObjByKeyId(keyId: number, visibility: number): any {
var p = this.protoStrategy; var p = this.protoStrategy;

View File

@ -661,17 +661,49 @@ export function main() {
expect(inj.get(NeedsService).service).toEqual('service'); expect(inj.get(NeedsService).service).toEqual('service');
}); });
it("should not instantiate other directives that depend on viewInjector bindings", it("should instantiate hostInjector injectables lazily", () => {
() => { var created = false;
var directiveAnnotation = new dirAnn.Component({ var inj = injector(
viewInjector: ListWrapper.concat([bind("service").toValue("service")], extraBindings) ListWrapper.concat([DirectiveBinding.createFromType(SimpleDirective, new dirAnn.Component({
}); hostInjector: [bind('service').toFactory(() => created = true)]
var componentDirective = }))],
DirectiveBinding.createFromType(SimpleDirective, directiveAnnotation); extraBindings),
expect(() => { injector([componentDirective, NeedsService], null); }) null, true);
.toThrowError(containsRegexp(
`No provider for service! (${stringify(NeedsService) } -> service)`)); 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", () => { it("should instantiate directives that depend on hostInjector bindings of other directives", () => {
var shadowInj = hostShadowInjectors( var shadowInj = hostShadowInjectors(

View File

@ -1103,6 +1103,32 @@ export function main() {
expect(parent.grandParentBus).toBe(grandParent.bus); expect(parent.grandParentBus).toBe(grandParent.bus);
expect(child.bus).toBe(parent.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: `
<component-providing-logging-injectable #providing>
<directive-consuming-injectable *ng-if="ctxBoolProp">
</directive-consuming-injectable>
</component-providing-logging-injectable>
`,
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(); async.done();
}); });
})); }));
@ -1701,6 +1727,23 @@ class DirectiveWithTwoWayBinding {
class InjectableService { 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]]}) @Directive({selector: 'directive-providing-injectable', hostInjector: [[InjectableService]]})
@Injectable() @Injectable()
class DirectiveProvidingInjectable { class DirectiveProvidingInjectable {