refactor(views): clean up creating views in place and extract view_hydrator

Major changes:
- `compiler.compileRoot(el, type)`
  -> `compiler.compileInHost(type) + viewHydrator.hydrateHostViewInPlace(el, view)`
- move all `hydrate`/`dehydrate` methods out of `View` and `ViewContainer` into
  a standalone class `view_hydrator` as private methods and provide new public
  methods dedicated to the individual use cases.

Note: This PR does not change the current functionality, only moves it
into different places.

See design discussion in #1351, in preparation for imperative views.
This commit is contained in:
Tobias Bosch
2015-04-15 21:51:30 -07:00
parent 97fc248e00
commit 923d90bce8
35 changed files with 1013 additions and 763 deletions
+5 -5
View File
@@ -361,7 +361,7 @@ export function main() {
});
}));
it('should create root proto views', inject([AsyncTestCompleter], (async) => {
it('should create host proto views', inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new View({template: '<div></div>'}));
var rootProtoView = createProtoView([
createComponentElementBinder(reader, MainComponent)
@@ -373,7 +373,7 @@ export function main() {
],
[rootProtoView, mainProtoView]
);
compiler.compileRoot(null, MainComponent).then( (protoView) => {
compiler.compileInHost(MainComponent).then( (protoView) => {
expect(protoView).toBe(rootProtoView);
expect(rootProtoView.elementBinders[0].nestedProtoView).toBe(mainProtoView);
async.done();
@@ -388,7 +388,7 @@ function createDirectiveBinding(reader, type) {
}
function createProtoView(elementBinders = null) {
var pv = new AppProtoView(null, null, null);
var pv = new AppProtoView(null, null);
if (isBlank(elementBinders)) {
elementBinders = [];
}
@@ -497,7 +497,7 @@ class FakeRenderer extends renderApi.Renderer {
return PromiseWrapper.resolve(ListWrapper.removeAt(this._results, 0));
}
createRootProtoView(elementOrSelector, componentId):Promise<renderApi.ProtoViewDto> {
createHostProtoView(componentId):Promise<renderApi.ProtoViewDto> {
return PromiseWrapper.resolve(
createRenderProtoView([createRenderComponentElementBinder(0)])
);
@@ -545,7 +545,7 @@ class FakeProtoViewFactory extends ProtoViewFactory {
_results:List;
constructor(results) {
super(null, null);
super(null);
this.requests = [];
this._results = results;
}
@@ -24,26 +24,32 @@ import {ElementRef, ElementInjector, ProtoElementInjector, PreBuiltObjects} from
import {Compiler} from 'angular2/src/core/compiler/compiler';
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
import {ViewFactory} from 'angular2/src/core/compiler/view_factory'
import {Renderer} from 'angular2/src/render/api';
import {AppViewHydrator} from 'angular2/src/core/compiler/view_hydrator';
export function main() {
describe("DynamicComponentLoader", () => {
var compiler;
var viewFactory;
var directiveMetadataReader;
var renderer;
var viewHydrator;
var loader;
beforeEach( () => {
compiler = new SpyCompiler();
viewFactory = new SpyViewFactory();
renderer = new SpyRenderer();
viewHydrator = new SpyAppViewHydrator();
directiveMetadataReader = new DirectiveMetadataReader();
loader = new DynamicComponentLoader(compiler, directiveMetadataReader, renderer, viewFactory);;
loader = new DynamicComponentLoader(compiler, directiveMetadataReader, viewFactory, viewHydrator);
});
function createProtoView() {
return new AppProtoView(null, null, null);
return new AppProtoView(null, null);
}
function createEmptyView() {
var view = new AppView(null, createProtoView(), MapWrapper.create());
view.init(null, [], [], [], [], []);
return view;
}
function createElementRef(view, boundElementIndex) {
@@ -67,23 +73,25 @@ export function main() {
});
});
it('should add the child view into the host view', inject([AsyncTestCompleter], (async) => {
it('should compile, create and hydrate the view', inject([AsyncTestCompleter], (async) => {
var log = [];
var hostView = new SpyAppView();
var childView = new SpyAppView();
hostView.spy('setDynamicComponentChildView').andCallFake( (boundElementIndex, childView) => {
ListWrapper.push(log, ['setDynamicComponentChildView', boundElementIndex, childView]);
var protoView = createProtoView();
var hostView = createEmptyView();
var childView = createEmptyView();
viewHydrator.spy('hydrateDynamicComponentView').andCallFake( (hostView, boundElementIndex,
componentView, componentDirective, injector) => {
ListWrapper.push(log, ['hydrateDynamicComponentView', hostView, boundElementIndex, componentView]);
});
childView.spy('hydrate').andCallFake( (appInjector, hostElementInjector, context, locals) => {
ListWrapper.push(log, 'hydrate');
viewFactory.spy('getView').andCallFake( (protoView) => {
ListWrapper.push(log, ['getView', protoView]);
return childView;
});
compiler.spy('compile').andCallFake( (_) => PromiseWrapper.resolve(createProtoView()));
viewFactory.spy('getView').andCallFake( (_) => childView);
compiler.spy('compile').andCallFake( (_) => PromiseWrapper.resolve(protoView));
var elementRef = createElementRef(hostView, 23);
loader.loadIntoExistingLocation(SomeComponent, elementRef).then( (componentRef) => {
expect(log[0]).toEqual('hydrate');
expect(log[1]).toEqual(['setDynamicComponentChildView', 23, childView]);
expect(log[0]).toEqual(['getView', protoView]);
expect(log[1]).toEqual(['hydrateDynamicComponentView', hostView, 23, childView]);
async.done();
});
}));
@@ -112,8 +120,8 @@ class SpyCompiler extends SpyObject {noSuchMethod(m){return super.noSuchMethod(m
class SpyViewFactory extends SpyObject {noSuchMethod(m){return super.noSuchMethod(m)}}
@proxy
@IMPLEMENTS(Renderer)
class SpyRenderer extends SpyObject {noSuchMethod(m){return super.noSuchMethod(m)}}
@IMPLEMENTS(AppViewHydrator)
class SpyAppViewHydrator extends SpyObject {noSuchMethod(m){return super.noSuchMethod(m)}}
@proxy
@IMPLEMENTS(AppView)
+15 -13
View File
@@ -614,7 +614,7 @@ export function main() {
});
it('should return viewContainer', function () {
var viewContainer = new ViewContainer(null, null, null, null);
var viewContainer = new ViewContainer(null, null, null, null, null);
var inj = injector([], null, null, new PreBuiltObjects(null, null, viewContainer, null));
expect(inj.get(ViewContainer)).toEqual(viewContainer);
@@ -631,21 +631,21 @@ export function main() {
describe("dynamicallyCreateComponent", () => {
it("should create a component dynamically", () => {
var inj = injector([]);
inj.dynamicallyCreateComponent(SimpleDirective, null, null);
inj.dynamicallyCreateComponent(DirectiveBinding.createFromType(SimpleDirective, null), null);
expect(inj.getDynamicallyLoadedComponent()).toBeAnInstanceOf(SimpleDirective);
expect(inj.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective);
});
it("should inject parent dependencies into the dynamically-loaded component", () => {
var inj = parentChildInjectors([SimpleDirective], []);
inj.dynamicallyCreateComponent(NeedDirectiveFromAncestor, null, null);
inj.dynamicallyCreateComponent(DirectiveBinding.createFromType(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 dynamically-loaded component", () => {
var injWithDynamicallyLoadedComponent = injector([SimpleDirective]);
injWithDynamicallyLoadedComponent.dynamicallyCreateComponent(SomeOtherDirective, null, null);
injWithDynamicallyLoadedComponent.dynamicallyCreateComponent(DirectiveBinding.createFromType(SomeOtherDirective, null), null);
var shadowDomProtoInjector = new ProtoElementInjector(null, 0, [NeedDirectiveFromAncestor], false);
var shadowDomInj = shadowDomProtoInjector.instantiate(null);
@@ -658,14 +658,14 @@ export function main() {
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);
inj.dynamicallyCreateComponent(DirectiveBinding.createFromType(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);
injWithDynamicallyLoadedComponent.dynamicallyCreateComponent(DirectiveBinding.createFromType(SimpleDirective, null), null);
var shadowDomProtoInjector = new ProtoElementInjector(null, 0, [NeedDirectiveFromAncestor], false);
var shadowDomInjector = shadowDomProtoInjector.instantiate(null);
@@ -678,8 +678,10 @@ export function main() {
it("should remove the dynamically-loaded component when dehydrating", () => {
var inj = injector([]);
inj.dynamicallyCreateComponent(
DirectiveWithDestroy,
new DummyDirective({lifecycle: [onDestroy]}),
DirectiveBinding.createFromType(
DirectiveWithDestroy,
new DummyDirective({lifecycle: [onDestroy]})
),
null);
var dir = inj.getDynamicallyLoadedComponent();
@@ -696,7 +698,7 @@ export function main() {
it("should inject services of the dynamically-loaded component", () => {
var inj = injector([]);
var appInjector = Injector.resolveAndCreate([bind("service").toValue("Service")]);
inj.dynamicallyCreateComponent(NeedsService, null, appInjector);
inj.dynamicallyCreateComponent(DirectiveBinding.createFromType(NeedsService, null), appInjector);
expect(inj.getDynamicallyLoadedComponent().service).toEqual("Service");
});
});
@@ -706,13 +708,13 @@ export function main() {
function createpreBuildObject(eventName, eventHandler) {
var handlers = StringMapWrapper.create();
StringMapWrapper.set(handlers, eventName, eventHandler);
var pv = new AppProtoView(null, null, null);
var pv = new AppProtoView(null, null);
pv.bindElement(null, 0, null, null, null);
var eventBindings = ListWrapper.create();
ListWrapper.push(eventBindings, new EventBinding(eventName, new Parser(new Lexer()).parseAction('handler()', '')));
pv.bindEvent(eventBindings);
var view = new AppView(pv, MapWrapper.create());
var view = new AppView(null, pv, MapWrapper.create());
view.context = new ContextWithHandler(eventHandler);
return new PreBuiltObjects(view, null, null, null);
}
@@ -751,8 +753,8 @@ export function main() {
beforeEach( () => {
renderer = new FakeRenderer();
var protoView = new AppProtoView(renderer, null, null);
view = new AppView(protoView, MapWrapper.create());
var protoView = new AppProtoView(null, null);
view = new AppView(renderer, protoView, MapWrapper.create());
view.render = new ViewRef();
});
+1 -2
View File
@@ -300,7 +300,6 @@ export function main() {
}));
tb.createView(MyComp, {context: ctx}).then((view) => {
view.detectChanges();
var childNodesOfWrapper = view.rootNodes[0].childNodes;
@@ -582,7 +581,7 @@ export function main() {
dispatchEvent(DOM.getGlobalEventTarget("document"), 'domEvent');
expect(listener.eventType).toEqual('document_domEvent');
view.rawView.dehydrate();
view.destroy();
listener = injector.get(DecoratorListeningDomEvent);
dispatchEvent(DOM.getGlobalEventTarget("body"), 'domEvent');
expect(listener.eventType).toEqual('');
+12 -2
View File
@@ -16,6 +16,7 @@ import {
} from 'angular2/test_lib';
import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang';
import {ViewFactory} from 'angular2/src/core/compiler/view_factory';
import {Renderer, ViewRef} from 'angular2/src/render/api';
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
import {DirectiveBinding, ElementInjector} from 'angular2/src/core/compiler/element_injector';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
@@ -26,13 +27,15 @@ import {ChangeDetector, ProtoChangeDetector} from 'angular2/change_detection';
export function main() {
describe('AppViewFactory', () => {
var reader;
var renderer;
beforeEach( () => {
renderer = new SpyRenderer();
reader = new DirectiveMetadataReader();
});
function createViewFactory({capacity}):ViewFactory {
return new ViewFactory(capacity);
return new ViewFactory(capacity, renderer);
}
function createProtoChangeDetector() {
@@ -47,7 +50,7 @@ export function main() {
if (isBlank(binders)) {
binders = [];
}
var pv = new AppProtoView(null, null, createProtoChangeDetector());
var pv = new AppProtoView(null, createProtoChangeDetector());
pv.elementBinders = binders;
return pv;
}
@@ -154,6 +157,13 @@ export function main() {
@Component({ selector: 'someComponent' })
class SomeComponent {}
@proxy
@IMPLEMENTS(Renderer)
class SpyRenderer extends SpyObject {
constructor(){super(Renderer);}
noSuchMethod(m){return super.noSuchMethod(m)}
}
@proxy
@IMPLEMENTS(ChangeDetector)
class SpyChangeDetector extends SpyObject {
@@ -24,15 +24,18 @@ import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {DirectiveBinding, ElementInjector} from 'angular2/src/core/compiler/element_injector';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {Component} from 'angular2/src/core/annotations/annotations';
import {AppViewHydrator} from 'angular2/src/core/compiler/view_hydrator';
export function main() {
describe('AppView', () => {
describe('AppViewHydrator', () => {
var renderer;
var reader;
var hydrator;
beforeEach( () => {
renderer = new SpyRenderer();
reader = new DirectiveMetadataReader();
hydrator = new AppViewHydrator(renderer);
});
function createDirectiveBinding(type) {
@@ -61,7 +64,7 @@ export function main() {
if (isBlank(binders)) {
binders = [];
}
var res = new AppProtoView(renderer, null, null);
var res = new AppProtoView(null, null);
res.elementBinders = binders;
return res;
}
@@ -75,8 +78,15 @@ export function main() {
]);
}
function createEmptyView() {
var view = new AppView(renderer, createProtoView(), MapWrapper.create());
var changeDetector = new SpyChangeDetector();
view.init(changeDetector, [], [], [], [], []);
return view;
}
function createHostView(pv, shadowView, componentInstance) {
var view = new AppView(pv, MapWrapper.create());
var view = new AppView(renderer, pv, MapWrapper.create());
var changeDetector = new SpyChangeDetector();
var eij = createElementInjector();
eij.spy('getComponent').andCallFake( () => componentInstance );
@@ -85,84 +95,93 @@ export function main() {
return view;
}
describe('setDynamicComponentChildView', () => {
function hydrate(view) {
hydrator.hydrateInPlaceHostView(null, null, view, null);
}
function dehydrate(view) {
hydrator.dehydrateInPlaceHostView(null, view);
}
describe('hydrateDynamicComponentView', () => {
it('should not allow to use non component indices', () => {
var pv = createProtoView([createEmptyElBinder()]);
var view = createHostView(pv, null, null);
var shadowView = new FakeAppView();
var shadowView = createEmptyView();
expect(
() => view.setDynamicComponentChildView(0, shadowView)
() => hydrator.hydrateDynamicComponentView(view, 0, shadowView, null, null)
).toThrowError('There is no dynamic component directive at element 0');
});
it('should not allow to use static component indices', () => {
var pv = createHostProtoView(createProtoView());
var view = createHostView(pv, null, null);
var shadowView = new FakeAppView();
var shadowView = createEmptyView();
expect(
() => view.setDynamicComponentChildView(0, shadowView)
() => hydrator.hydrateDynamicComponentView(view, 0, shadowView, null, null)
).toThrowError('There is no dynamic component directive at element 0');
});
it('should not allow to overwrite an existing component', () => {
var pv = createHostProtoView(null);
var shadowView = new FakeAppView();
var shadowView = createEmptyView();
var view = createHostView(pv, null, null);
view.setDynamicComponentChildView(0, shadowView);
renderer.spy('createDynamicComponentView').andCallFake( (a,b,c) => {
return [new ViewRef(), new ViewRef()];
});
hydrator.hydrateDynamicComponentView(view, 0, shadowView, createDirectiveBinding(SomeComponent), null);
expect(
() => view.setDynamicComponentChildView(0, shadowView)
() => hydrator.hydrateDynamicComponentView(view, 0, shadowView, null, null)
).toThrowError('There already is a bound component at element 0');
});
});
describe('hydrate', () => {
describe('hydrate... shared functionality', () => {
it('should hydrate existing child components', () => {
var hostPv = createHostProtoView(createProtoView());
var componentInstance = {};
var shadowView = new FakeAppView();
var shadowView = createEmptyView();
var hostView = createHostView(hostPv, shadowView, componentInstance);
renderer.spy('createView').andCallFake( (_) => {
renderer.spy('createInPlaceHostView').andCallFake( (a,b,c) => {
return [new ViewRef(), new ViewRef()];
});
hostView.hydrate(null, null, null, null);
hydrate(hostView);
expect(shadowView.spy('hydrate')).not.toHaveBeenCalled();
expect(shadowView.spy('internalHydrateRecurse')).toHaveBeenCalled();
expect(shadowView.hydrated()).toBe(true);
});
});
describe('dehydrate', () => {
describe('dehydrate... shared functionality', () => {
var hostView;
var shadowView;
function createAndHydrate(nestedProtoView) {
var componentInstance = {};
shadowView = new FakeAppView();
shadowView = createEmptyView();
var hostPv = createHostProtoView(nestedProtoView);
hostView = createHostView(hostPv, shadowView, componentInstance);
renderer.spy('createView').andCallFake( (_) => {
renderer.spy('createInPlaceHostView').andCallFake( (a,b,c) => {
return [new ViewRef(), new ViewRef()];
});
hostView.hydrate(null, null, null, null);
hydrate(hostView);
}
it('should dehydrate child components', () => {
createAndHydrate(createProtoView());
hostView.dehydrate();
dehydrate(hostView);
expect(shadowView.spy('dehydrate')).not.toHaveBeenCalled();
expect(shadowView.spy('internalDehydrateRecurse')).toHaveBeenCalled();
expect(shadowView.hydrated()).toBe(false);
});
it('should not clear static child components', () => {
createAndHydrate(createProtoView());
hostView.dehydrate();
dehydrate(hostView);
expect(hostView.componentChildViews[0]).toBe(shadowView);
expect(hostView.changeDetector.spy('removeShadowDomChild')).not.toHaveBeenCalled();
@@ -170,7 +189,7 @@ export function main() {
it('should clear dynamic child components', () => {
createAndHydrate(null);
hostView.dehydrate();
dehydrate(hostView);
expect(hostView.componentChildViews[0]).toBe(null);
expect(hostView.changeDetector.spy('removeShadowDomChild')).toHaveBeenCalledWith(shadowView.changeDetector);
@@ -204,10 +223,3 @@ class SpyElementInjector extends SpyObject {
constructor(){super(ElementInjector);}
noSuchMethod(m){return super.noSuchMethod(m)}
}
@proxy
@IMPLEMENTS(AppView)
class FakeAppView extends SpyObject {
constructor(){super(AppView);}
noSuchMethod(m){return super.noSuchMethod(m)}
}