feat(view): generalized loading of dynamic components

This commit is contained in:
vsavkin
2015-04-06 13:19:30 -07:00
parent e9f70293ac
commit f45281a10a
19 changed files with 424 additions and 245 deletions
+2 -4
View File
@@ -98,9 +98,8 @@ export function main() {
it('should throw if bootstrapped Directive is not a Component', inject([AsyncTestCompleter], (async) => {
var injectorPromise = bootstrap(HelloRootDirectiveIsNotCmp, testBindings, (e,t) => {throw e;});
PromiseWrapper.then(injectorPromise, null, (reason) => {
expect(reason.message).toContain('Only Components can be bootstrapped; ' +
'Directive of HelloRootDirectiveIsNotCmp is not a Component');
async.done();
expect(reason.message).toContain(`Could not load 'HelloRootDirectiveIsNotCmp' because it is not a component.`);
async.done();
});
}));
@@ -137,7 +136,6 @@ export function main() {
it('should display hello world', inject([AsyncTestCompleter], (async) => {
var injectorPromise = bootstrap(HelloRootCmp, testBindings);
injectorPromise.then((injector) => {
expect(injector.get(appElementToken)).toHaveText('hello world!');
async.done();
});
@@ -1,6 +1,6 @@
import {ddescribe, describe, it, iit, expect, beforeEach} from 'angular2/test_lib';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {PrivateComponentLoader} from 'angular2/src/core/compiler/private_component_loader';
import {DynamicComponentLoader, DirectiveRef} from 'angular2/src/core/compiler/dynamic_component_loader';
import {Decorator, Viewport} from 'angular2/src/core/annotations/annotations';
@Decorator({selector: 'someDecorator'})
@@ -10,23 +10,26 @@ class SomeDecorator {}
class SomeViewport {}
export function main() {
describe("PrivateComponentLoader", () => {
describe("DynamicComponentLoader", () => {
var loader;
beforeEach(() => {
loader = new PrivateComponentLoader(null, new DirectiveMetadataReader(), null);
loader = new DynamicComponentLoader(null, new DirectiveMetadataReader(), null, null);
});
describe('Load errors', () => {
it('should throw when trying to load a decorator', () => {
expect(() => loader.load(SomeDecorator, null))
describe("loadIntoExistingLocation", () => {
describe('Load errors', () => {
it('should throw when trying to load a decorator', () => {
expect(() => loader.loadIntoExistingLocation(SomeDecorator, null))
.toThrowError("Could not load 'SomeDecorator' because it is not a component.");
});
});
it('should throw when trying to load a viewport', () => {
expect(() => loader.load(SomeViewport, null))
it('should throw when trying to load a viewport', () => {
expect(() => loader.loadIntoExistingLocation(SomeViewport, null))
.toThrowError("Could not load 'SomeViewport' because it is not a component.");
});
});
});
});
}
+41 -23
View File
@@ -621,58 +621,76 @@ export function main() {
});
});
describe("createPrivateComponent", () => {
it("should create a private component", () => {
describe("dynamicallyCreateComponent", () => {
it("should create a component dynamically", () => {
var inj = injector([]);
inj.createPrivateComponent(SimpleDirective, null);
expect(inj.getPrivateComponent()).toBeAnInstanceOf(SimpleDirective);
inj.dynamicallyCreateComponent(SimpleDirective, null, null);
expect(inj.getDynamicallyLoadedComponent()).toBeAnInstanceOf(SimpleDirective);
expect(inj.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective);
});
it("should inject parent dependencies into the private component", () => {
it("should inject parent dependencies into the dynamically-loaded component", () => {
var inj = parentChildInjectors([SimpleDirective], []);
inj.createPrivateComponent(NeedDirectiveFromAncestor, null);
expect(inj.getPrivateComponent()).toBeAnInstanceOf(NeedDirectiveFromAncestor);
expect(inj.getPrivateComponent().dependency).toBeAnInstanceOf(SimpleDirective);
inj.dynamicallyCreateComponent(NeedDirectiveFromAncestor, null, null);
expect(inj.getDynamicallyLoadedComponent()).toBeAnInstanceOf(NeedDirectiveFromAncestor);
expect(inj.getDynamicallyLoadedComponent().dependency).toBeAnInstanceOf(SimpleDirective);
});
it("should not inject the proxy component into the children of the private component", () => {
var injWithPrivateComponent = injector([SimpleDirective]);
injWithPrivateComponent.createPrivateComponent(SomeOtherDirective, null);
it("should not inject the proxy component into the children of the dynamically-loaded component", () => {
var injWithDynamicallyLoadedComponent = injector([SimpleDirective]);
injWithDynamicallyLoadedComponent.dynamicallyCreateComponent(SomeOtherDirective, null, null);
var shadowDomProtoInjector = new ProtoElementInjector(null, 0, [NeedDirectiveFromAncestor], false);
var shadowDomInj = shadowDomProtoInjector.instantiate(null);
expect(() => shadowDomInj.instantiateDirectives(appInjector, injWithPrivateComponent, null, defaultPreBuiltObjects)).
expect(() =>
shadowDomInj.instantiateDirectives(appInjector, injWithDynamicallyLoadedComponent,null, defaultPreBuiltObjects)).
toThrowError(new RegExp("No provider for SimpleDirective"));
});
it("should inject the private component into the children of the private component", () => {
var injWithPrivateComponent = injector([]);
injWithPrivateComponent.createPrivateComponent(SimpleDirective, null);
it("should not inject the dynamically-loaded component into directives on the same element", () => {
var proto = new ProtoElementInjector(null, 0, [NeedsDirective], false);
var inj = proto.instantiate(null);
inj.dynamicallyCreateComponent(SimpleDirective, null, null);
expect(() => inj.instantiateDirectives(null, null, null, null)).toThrowError();
});
it("should inject the dynamically-loaded component into the children of the dynamically-loaded component", () => {
var injWithDynamicallyLoadedComponent = injector([]);
injWithDynamicallyLoadedComponent.dynamicallyCreateComponent(SimpleDirective, null, null);
var shadowDomProtoInjector = new ProtoElementInjector(null, 0, [NeedDirectiveFromAncestor], false);
var shadowDomInjector = shadowDomProtoInjector.instantiate(null);
shadowDomInjector.instantiateDirectives(appInjector, injWithPrivateComponent, null, defaultPreBuiltObjects);
shadowDomInjector.instantiateDirectives(appInjector, injWithDynamicallyLoadedComponent, null, defaultPreBuiltObjects);
expect(shadowDomInjector.get(NeedDirectiveFromAncestor)).toBeAnInstanceOf(NeedDirectiveFromAncestor);
expect(shadowDomInjector.get(NeedDirectiveFromAncestor).dependency).toBeAnInstanceOf(SimpleDirective);
});
it("should support rehydrating the private component", () => {
it("should remove the dynamically-loaded component when dehydrating", () => {
var inj = injector([]);
inj.createPrivateComponent(
DirectiveWithDestroy,
new DummyDirective({lifecycle: [onDestroy]}));
var dir = inj.getPrivateComponent();
inj.dynamicallyCreateComponent(
DirectiveWithDestroy,
new DummyDirective({lifecycle: [onDestroy]}),
null);
var dir = inj.getDynamicallyLoadedComponent();
inj.clearDirectives();
expect(inj.getPrivateComponent()).toBe(null);
expect(inj.getDynamicallyLoadedComponent()).toBe(null);
expect(dir.onDestroyCounter).toBe(1);
inj.instantiateDirectives(null, null, null, null);
expect(inj.getPrivateComponent()).not.toBe(null);
expect(inj.getDynamicallyLoadedComponent()).toBe(null);
});
it("should inject services of the dynamically-loaded component", () => {
var inj = injector([]);
var appInjector = new Injector([bind("service").toValue("Service")]);
inj.dynamicallyCreateComponent(NeedsService, null, appInjector);
expect(inj.getDynamicallyLoadedComponent().service).toEqual("Service");
});
});
+43 -23
View File
@@ -24,13 +24,12 @@ import {Injector, bind} from 'angular2/di';
import {dynamicChangeDetection,
ChangeDetection, DynamicChangeDetection, Pipe, PipeRegistry, BindingPropagationConfig, ON_PUSH} from 'angular2/change_detection';
import {PrivateComponentLocation} from 'angular2/src/core/compiler/private_component_location';
import {PrivateComponentLoader} from 'angular2/src/core/compiler/private_component_loader';
import {Decorator, Component, Viewport, DynamicComponent} from 'angular2/src/core/annotations/annotations';
import {Template} from 'angular2/src/core/annotations/template';
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
import {EventEmitter, Attribute} from 'angular2/src/core/annotations/di';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
import {DirectiveRef} from 'angular2/src/core/compiler/element_injector';
import {If} from 'angular2/src/directives/if';
@@ -544,23 +543,40 @@ export function main() {
}));
}
it('should support dynamic components', inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideTemplate(MyComp, new Template({
inline: '<dynamic-comp #dynamic></dynamic-comp>',
directives: [DynamicComp]
}));
tb.createView(MyComp, {context: ctx}).then((view) => {
describe("dynamic components", () => {
it('should support loading components dynamically', inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideTemplate(MyComp, new Template({
inline: '<dynamic-comp #dynamic></dynamic-comp>',
directives: [DynamicComp]
}));
var dynamicComponent = view.rawView.locals.get("dynamic");
expect(dynamicComponent).toBeAnInstanceOf(DynamicComp);
tb.createView(MyComp).then((view) => {
var dynamicComponent = view.rawView.locals.get("dynamic");
expect(dynamicComponent).toBeAnInstanceOf(DynamicComp);
dynamicComponent.done.then((_) => {
view.detectChanges();
expect(view.rootNodes).toHaveText('hello');
async.done();
dynamicComponent.done.then((_) => {
view.detectChanges();
expect(view.rootNodes).toHaveText('hello');
async.done();
});
});
});
}));
}));
it('should inject dependencies of the dynamically-loaded component', inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideTemplate(MyComp, new Template({
inline: '<dynamic-comp #dynamic></dynamic-comp>',
directives: [DynamicComp]
}));
tb.createView(MyComp).then((view) => {
var dynamicComponent = view.rawView.locals.get("dynamic");
dynamicComponent.done.then((ref) => {
expect(ref.instance.dynamicallyCreatedComponentService).toBeAnInstanceOf(DynamicallyCreatedComponentService);
async.done();
});
});
}));
});
it('should support static attributes', inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideTemplate(MyComp, new Template({
@@ -568,7 +584,6 @@ export function main() {
directives: [NeedsAttribute]
}));
tb.createView(MyComp, {context: ctx}).then((view) => {
var injector = view.rawView.elementInjectors[0];
var needsAttribute = injector.get(NeedsAttribute);
expect(needsAttribute.typeAttribute).toEqual('text');
@@ -643,27 +658,32 @@ export function main() {
});
}
class DynamicallyCreatedComponentService {
}
@DynamicComponent({
selector: 'dynamic-comp'
})
class DynamicComp {
done;
constructor(loader:PrivateComponentLoader, location:PrivateComponentLocation) {
this.done = loader.load(HelloCmp, location);
constructor(loader:DynamicComponentLoader, self:DirectiveRef) {
this.done = loader.loadIntoExistingLocation(DynamicallyCreatedCmp, self);
}
}
@Component({
selector: 'hello-cmp'
selector: 'hello-cmp',
services: [DynamicallyCreatedComponentService]
})
@Template({
inline: "{{greeting}}"
})
class HelloCmp {
class DynamicallyCreatedCmp {
greeting:string;
constructor() {
dynamicallyCreatedComponentService:DynamicallyCreatedComponentService;
constructor(a:DynamicallyCreatedComponentService) {
this.greeting = "hello";
this.dynamicallyCreatedComponentService = a;
}
}