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
@@ -36,14 +36,14 @@ export function main() {
});
renderer = testbed.renderer;
eventPlugin = testbed.eventPlugin;
compileRoot = (rootEl, componentId) => testbed.compileRoot(rootEl, componentId);
compileRoot = (componentId) => testbed.compileRoot(componentId);
}
it('should create root views while using the given elements in place', inject([AsyncTestCompleter], (async) => {
it('should create host views while using the given elements in place', inject([AsyncTestCompleter], (async) => {
createRenderer();
renderer.createRootProtoView(rootEl, 'someComponentId').then( (rootProtoView) => {
renderer.createHostProtoView('someComponentId').then( (rootProtoView) => {
expect(rootProtoView.elementBinders[0].directives[0].directiveIndex).toBe(0);
var viewRefs = renderer.createView(rootProtoView.render);
var viewRefs = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render);
expect(viewRefs.length).toBe(1);
expect(viewRefs[0].delegate.rootNodes[0]).toEqual(rootEl);
async.done();
@@ -52,7 +52,7 @@ export function main() {
it('should add a static component', inject([AsyncTestCompleter], (async) => {
createRenderer();
renderer.createRootProtoView(rootEl, 'someComponentId').then( (rootProtoView) => {
renderer.createHostProtoView('someComponentId').then( (rootProtoView) => {
var template = new ViewDefinition({
componentId: 'someComponent',
template: 'hello',
@@ -60,7 +60,7 @@ export function main() {
});
renderer.compile(template).then( (pv) => {
renderer.mergeChildComponentProtoViews(rootProtoView.render, [pv.render]);
renderer.createView(rootProtoView.render);
renderer.createInPlaceHostView(null, rootEl, rootProtoView.render);
expect(rootEl).toHaveText('hello');
async.done();
});
@@ -69,16 +69,15 @@ export function main() {
it('should add a a dynamic component', inject([AsyncTestCompleter], (async) => {
createRenderer();
renderer.createRootProtoView(rootEl, 'someComponentId').then( (rootProtoView) => {
renderer.createHostProtoView('someComponentId').then( (rootProtoView) => {
var template = new ViewDefinition({
componentId: 'someComponent',
template: 'hello',
directives: []
});
renderer.compile(template).then( (pv) => {
var rootViewRef = renderer.createView(rootProtoView.render)[0];
var childComponentViewRef = renderer.createView(pv.render)[0];
renderer.setDynamicComponentView(rootViewRef, 0, childComponentViewRef);
var rootViewRef = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render)[0];
renderer.createDynamicComponentView(rootViewRef, 0, pv.render)[0];
expect(rootEl).toHaveText('hello');
async.done();
});
@@ -93,8 +92,8 @@ export function main() {
directives: []
})]
});
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRefs = renderer.createView(rootProtoView.render);
compileRoot('someComponent').then( (rootProtoView) => {
var viewRefs = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render);
renderer.setText(viewRefs[1], 0, 'hello');
expect(rootEl).toHaveText('hello');
async.done();
@@ -109,8 +108,8 @@ export function main() {
directives: []
})]
});
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRefs = renderer.createView(rootProtoView.render);
compileRoot('someComponent').then( (rootProtoView) => {
var viewRefs = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render);
renderer.setElementProperty(viewRefs[1], 0, 'value', 'hello');
expect(DOM.childNodes(rootEl)[0].value).toEqual('hello');
async.done();
@@ -125,18 +124,20 @@ export function main() {
directives: []
})]
});
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRef = renderer.createView(rootProtoView.render)[1];
compileRoot('someComponent').then( (rootProtoView) => {
var viewRef = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render)[1];
var vcProtoViewRef = rootProtoView.elementBinders[0]
.nestedProtoView.elementBinders[0].nestedProtoView.render;
var vcRef = new ViewContainerRef(viewRef, 0);
var childViewRef = renderer.createView(vcProtoViewRef)[0];
expect(rootEl).toHaveText('');
renderer.insertViewIntoContainer(vcRef, childViewRef);
var childViewRef = renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0];
expect(rootEl).toHaveText('hello');
renderer.detachViewFromContainer(vcRef, 0);
expect(rootEl).toHaveText('');
renderer.insertViewIntoContainer(vcRef, 0, childViewRef);
expect(rootEl).toHaveText('hello');
renderer.destroyViewInContainer(vcRef, 0);
expect(rootEl).toHaveText('');
async.done();
});
@@ -151,14 +152,16 @@ export function main() {
})],
viewCacheCapacity: 2
});
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
compileRoot('someComponent').then( (rootProtoView) => {
var viewRef = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render)[1];
var vcProtoViewRef = rootProtoView.elementBinders[0]
.nestedProtoView.elementBinders[0].nestedProtoView.render;
var vcRef = new ViewContainerRef(viewRef, 0);
var viewRef1 = renderer.createView(vcProtoViewRef)[0];
renderer.destroyView(viewRef1);
var viewRef2 = renderer.createView(vcProtoViewRef)[0];
var viewRef3 = renderer.createView(vcProtoViewRef)[0];
var viewRef1 = renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0];
renderer.destroyViewInContainer(vcRef, 0);
var viewRef2 = renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0];
var viewRef3 = renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0];
expect(viewRef2.delegate).toBe(viewRef1.delegate);
expect(viewRef3.delegate).not.toBe(viewRef1.delegate);
@@ -176,8 +179,8 @@ export function main() {
directives: []
})]
});
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRef = renderer.createView(rootProtoView.render)[1];
compileRoot('someComponent').then( (rootProtoView) => {
var viewRef = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render)[1];
var dispatcher = new LoggingEventDispatcher();
renderer.setEventDispatcher(viewRef, dispatcher);
var inputEl = DOM.childNodes(rootEl)[0];
+5 -3
View File
@@ -15,6 +15,7 @@ import {EventManager, EventManagerPlugin} from 'angular2/src/render/dom/events/e
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
import {ViewFactory} from 'angular2/src/render/dom/view/view_factory';
import {RenderViewHydrator} from 'angular2/src/render/dom/view/view_hydrator';
export class IntegrationTestbed {
renderer;
@@ -45,11 +46,12 @@ export class IntegrationTestbed {
this.eventPlugin = new FakeEventManagerPlugin();
var eventManager = new EventManager([this.eventPlugin], new FakeVmTurnZone());
var viewFactory = new ViewFactory(viewCacheCapacity, eventManager, shadowDomStrategy);
this.renderer = new DirectDomRenderer(compiler, viewFactory, shadowDomStrategy);
var viewHydrator = new RenderViewHydrator(eventManager, viewFactory);
this.renderer = new DirectDomRenderer(compiler, viewFactory, viewHydrator, shadowDomStrategy);
}
compileRoot(rootEl, componentId):Promise<ProtoViewDto> {
return this.renderer.createRootProtoView(rootEl, componentId).then( (rootProtoView) => {
compileRoot(componentId):Promise<ProtoViewDto> {
return this.renderer.createHostProtoView(componentId).then( (rootProtoView) => {
return this._compileNestedProtoViews(rootProtoView, [
new DirectiveMetadata({
type: DirectiveMetadata.COMPONENT_TYPE,
@@ -31,7 +31,7 @@ import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {RenderView} from 'angular2/src/render/dom/view/view';
export function main() {
describe('EmulatedScoped', () => {
describe('EmulatedScopedShadowDomStrategy', () => {
var xhr, styleHost, strategy;
beforeEach(() => {
@@ -47,7 +47,7 @@ export function main() {
it('should attach the view nodes as child of the host element', () => {
var host = el('<div><span>original content</span></div>');
var nodes = el('<div>view</div>');
var view = new RenderView(null, [nodes], [], [], [], [], null);
var view = new RenderView(null, [nodes], [], [], [], []);
strategy.attachTemplate(host, view);
var firstChild = DOM.firstChild(host);
@@ -28,7 +28,7 @@ import {RenderView} from 'angular2/src/render/dom/view/view';
export function main() {
var strategy;
describe('EmulatedUnscoped', () => {
describe('EmulatedUnscopedShadowDomStrategy', () => {
var styleHost;
beforeEach(() => {
@@ -42,7 +42,7 @@ export function main() {
it('should attach the view nodes as child of the host element', () => {
var host = el('<div><span>original content</span></div>');
var nodes = el('<div>view</div>');
var view = new RenderView(null, [nodes], [], [], [], [], null);
var view = new RenderView(null, [nodes], [], [], [], []);
strategy.attachTemplate(host, view);
var firstChild = DOM.firstChild(host);
@@ -35,7 +35,7 @@ export function main() {
it('should attach the view nodes to the shadow root', () => {
var host = el('<div><span>original content</span></div>');
var nodes = el('<div>view</div>');
var view = new RenderView(null, [nodes], [], [], [], [], null);
var view = new RenderView(null, [nodes], [], [], [], []);
strategy.attachTemplate(host, view);
var shadowRoot = DOM.getShadowRoot(host);
@@ -60,7 +60,7 @@ export function main() {
templates: ListWrapper.concat(templates, componentTemplates)
});
renderer = testbed.renderer;
compileRoot = (rootEl, componentId) => testbed.compileRoot(rootEl, componentId);
compileRoot = (rootEl) => testbed.compileRoot(rootEl);
compile = (componentId) => testbed.compile(componentId);
}
@@ -78,8 +78,8 @@ export function main() {
directives: [simple]
})]
});
compileRoot(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
compileRoot('main').then( (pv) => {
renderer.createInPlaceHostView(null, rootEl, pv.render);
expect(rootEl).toHaveText('SIMPLE(A)');
@@ -97,11 +97,10 @@ export function main() {
directives: [dynamicComponent]
})]
});
compileRoot(rootEl, 'main').then( (rootPv) => {
compileRoot('main').then( (rootPv) => {
compile('simple').then( (simplePv) => {
var views = renderer.createView(rootPv.render);
var simpleViews = renderer.createView(simplePv.render);
renderer.setDynamicComponentView(views[1], 0, simpleViews[0]);
var views = renderer.createInPlaceHostView(null, rootEl, rootPv.render);
renderer.createDynamicComponentView(views[1], 0, simplePv.render);
expect(rootEl).toHaveText('SIMPLE(A)');
@@ -122,8 +121,8 @@ export function main() {
directives: [multipleContentTagsComponent]
})]
});
compileRoot(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
compileRoot('main').then( (pv) => {
renderer.createInPlaceHostView(null, rootEl, pv.render);
expect(rootEl).toHaveText('(A, BC)');
@@ -142,8 +141,8 @@ export function main() {
directives: [multipleContentTagsComponent]
})]
});
compileRoot(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
compileRoot('main').then( (pv) => {
renderer.createInPlaceHostView(null, rootEl, pv.render);
expect(rootEl).toHaveText('(, BAC)');
@@ -162,20 +161,18 @@ export function main() {
directives: [multipleContentTagsComponent, manualViewportDirective]
})]
});
compileRoot(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
compileRoot('main').then( (pv) => {
var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render);
var vcRef = new ViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
.elementBinders[1].nestedProtoView.render;
var childViewRef = renderer.createView(vcProtoViewRef)[0];
expect(rootEl).toHaveText('(, B)');
renderer.insertViewIntoContainer(vcRef, childViewRef);
renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0];
expect(rootEl).toHaveText('(, AB)');
renderer.detachViewFromContainer(vcRef, 0);
renderer.destroyViewInContainer(vcRef, 0);
expect(rootEl).toHaveText('(, B)');
@@ -194,20 +191,18 @@ export function main() {
directives: [multipleContentTagsComponent, manualViewportDirective]
})]
});
compileRoot(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
compileRoot('main').then( (pv) => {
var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render);
var vcRef = new ViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
.elementBinders[1].nestedProtoView.render;
var childViewRef = renderer.createView(vcProtoViewRef)[0];
expect(rootEl).toHaveText('(, B)');
renderer.insertViewIntoContainer(vcRef, childViewRef);
renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0];
expect(rootEl).toHaveText('(A, B)');
renderer.detachViewFromContainer(vcRef, 0);
renderer.destroyViewInContainer(vcRef, 0);
expect(rootEl).toHaveText('(, B)');
@@ -226,8 +221,8 @@ export function main() {
directives: [outerWithIndirectNestedComponent]
})]
});
compileRoot(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
compileRoot('main').then( (pv) => {
renderer.createInPlaceHostView(null, rootEl, pv.render);
expect(rootEl).toHaveText('OUTER(SIMPLE(AB))');
@@ -247,16 +242,14 @@ export function main() {
directives: [outerComponent, manualViewportDirective]
})]
});
compileRoot(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
compileRoot('main').then( (pv) => {
var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render);
var vcRef = new ViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
.elementBinders[1].nestedProtoView.render;
var childViewRef = renderer.createView(vcProtoViewRef)[0];
expect(rootEl).toHaveText('OUTER(INNER(INNERINNER(,BC)))');
renderer.insertViewIntoContainer(vcRef, childViewRef);
renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0];
expect(rootEl).toHaveText('OUTER(INNER(INNERINNER(A,BC)))');
async.done();
@@ -275,21 +268,20 @@ export function main() {
directives: [conditionalContentComponent]
})]
});
compileRoot(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
compileRoot('main').then( (pv) => {
var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render);
var vcRef = new ViewContainerRef(viewRefs[2], 0);
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
.elementBinders[0].nestedProtoView
.elementBinders[0].nestedProtoView.render;
var childViewRef = renderer.createView(vcProtoViewRef)[0];
expect(rootEl).toHaveText('(, ABC)');
renderer.insertViewIntoContainer(vcRef, childViewRef);
renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0];
expect(rootEl).toHaveText('(A, BC)');
renderer.detachViewFromContainer(vcRef, 0);
renderer.destroyViewInContainer(vcRef, 0);
expect(rootEl).toHaveText('(, ABC)');
@@ -42,7 +42,6 @@ export function main() {
}
return new RenderProtoView({
element: rootEl,
isRootView: false,
elementBinders: binders
});
}
@@ -23,12 +23,16 @@ import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_s
import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom';
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ViewFactory} from 'angular2/src/render/dom/view/view_factory';
import {RenderViewHydrator} from 'angular2/src/render/dom/view/view_hydrator';
export function main() {
describe('RenderView', () => {
describe('RenderViewHydrator', () => {
var shadowDomStrategy;
var eventManager;
var viewFactory;
var viewHydrator;
function createProtoView({rootEl, binders}={}) {
if (isBlank(rootEl)) {
@@ -39,7 +43,6 @@ export function main() {
}
return new RenderProtoView({
element: rootEl,
isRootView: false,
elementBinders: binders
});
}
@@ -67,83 +70,85 @@ export function main() {
function createEmptyView() {
var root = el('<div><div></div></div>');
return new RenderView(createProtoView(), [DOM.childNodes(root)[0]],
[], [], [], [], eventManager);
[], [], [], []);
}
function createHostView(pv, shadowDomView) {
var view = new RenderView(pv, [el('<div></div>')],
[], [el('<div></div>')], [], [], eventManager);
view.setComponentView(shadowDomStrategy, 0, shadowDomView);
[], [el('<div></div>')], [], []);
viewFactory.setComponentView(view, 0, shadowDomView);
return view;
}
function hydrate(view) {
viewHydrator.hydrateInPlaceHostView(null, view);
}
function dehydrate(view) {
viewHydrator.dehydrateInPlaceHostView(null, view);
}
beforeEach( () => {
eventManager = new SpyEventManager();
shadowDomStrategy = new SpyShadowDomStrategy();
shadowDomStrategy.spy('constructLightDom').andCallFake( (lightDomView, shadowDomView, el) => {
return new SpyLightDom();
});
viewFactory = new ViewFactory(1, eventManager, shadowDomStrategy);
viewHydrator = new RenderViewHydrator(eventManager, viewFactory);
});
describe('setComponentView', () => {
describe('hydrateDynamicComponentView', () => {
it('should redistribute when a component is added to a hydrated view', () => {
var shadowView = new SpyRenderView();
it('should redistribute', () => {
var shadowView = createEmptyView();
var hostPv = createHostProtoView(createProtoView());
var hostView = createHostView(hostPv, shadowView);
hostView.hydrate(null);
hostView.setComponentView(shadowDomStrategy, 0, shadowView);
viewHydrator.hydrateDynamicComponentView(hostView, 0, shadowView);
var lightDomSpy:SpyLightDom = hostView.lightDoms[0];
expect(lightDomSpy.spy('redistribute')).toHaveBeenCalled();
});
it('should not redistribute when a component is added to a dehydrated view', () => {
var shadowView = new SpyRenderView();
var hostPv = createHostProtoView(createProtoView());
var hostView = createHostView(hostPv, shadowView);
hostView.setComponentView(shadowDomStrategy, 0, shadowView);
var lightDomSpy:SpyLightDom = hostView.lightDoms[0];
expect(lightDomSpy.spy('redistribute')).not.toHaveBeenCalled();
});
});
describe('hydrate', () => {
describe('hydrate... shared functionality', () => {
it('should hydrate existing child components', () => {
var hostPv = createHostProtoView(createProtoView());
var shadowView = new SpyRenderView();
var hostView = createHostView(hostPv, shadowView);
var shadowView = createEmptyView();
createHostView(hostPv, shadowView);
hostView.hydrate(null);
hydrate(shadowView);
expect(shadowView.spy('hydrate')).toHaveBeenCalled();
expect(shadowView.hydrated).toBe(true);
});
});
describe('dehydrate', () => {
describe('dehydrate... shared functionality', () => {
var hostView;
function createAndHydrate(nestedProtoView, shadowView) {
var hostPv = createHostProtoView(nestedProtoView);
hostView = createHostView(hostPv, shadowView);
hostView.hydrate(null);
hydrate(hostView);
}
it('should dehydrate child components', () => {
var shadowView = new SpyRenderView();
var shadowView = createEmptyView();
createAndHydrate(createProtoView(), shadowView);
hostView.dehydrate();
expect(shadowView.spy('dehydrate')).toHaveBeenCalled();
expect(shadowView.hydrated).toBe(true);
dehydrate(hostView);
expect(shadowView.hydrated).toBe(false);
});
it('should not clear static child components', () => {
var shadowView = createEmptyView();
createAndHydrate(createProtoView(), shadowView);
hostView.dehydrate();
dehydrate(hostView);
expect(hostView.componentChildViews[0]).toBe(shadowView);
expect(shadowView.rootNodes[0].parentNode).toBeTruthy();
@@ -152,7 +157,7 @@ export function main() {
it('should clear dynamic child components', () => {
var shadowView = createEmptyView();
createAndHydrate(null, shadowView);
hostView.dehydrate();
dehydrate(hostView);
expect(hostView.componentChildViews[0]).toBe(null);
expect(shadowView.rootNodes[0].parentNode).toBe(null);
@@ -184,10 +189,3 @@ class SpyLightDom extends SpyObject {
noSuchMethod(m){return super.noSuchMethod(m)}
}
@proxy
@IMPLEMENTS(RenderView)
class SpyRenderView extends SpyObject {
constructor(){super(RenderView);}
noSuchMethod(m){return super.noSuchMethod(m)}
}