refactor(view_manager): split inPlace views into root and free host views.

BREAKING CHANGE:
`AppViewManager.createInPlaceHostView` is replaced by
`AppViewManager.createRootHostView` (for bootstrap) and
`AppViewManager.createFreeHostView` (for imperative components).

The later creates new host elements that are not attached anywhere.
To attach them, use `DomRenderer.getHostElement(hostviewRef)`
to get the host element.

Closes #1920
This commit is contained in:
Tobias Bosch
2015-05-15 09:55:43 -07:00
parent a38a0d6f87
commit 421d8916a6
14 changed files with 310 additions and 158 deletions

View File

@ -54,13 +54,10 @@ function _injectorBindings(appComponentType): List<Binding> {
return [
bind(DOCUMENT_TOKEN).toValue(DOM.defaultDoc()),
bind(appComponentRefToken).toAsyncFactory((dynamicComponentLoader, injector,
metadataReader, testability, registry) => {
testability, registry) => {
var annotation = metadataReader.resolve(appComponentType);
var selector = annotation.selector;
// TODO(rado): investigate whether to support bindings on root component.
return dynamicComponentLoader.loadIntoNewLocation(appComponentType, null, selector, injector).then( (componentRef) => {
return dynamicComponentLoader.loadAsRoot(appComponentType, null, injector).then( (componentRef) => {
var domView = resolveInternalDomView(componentRef.hostView.render);
// We need to do this here to ensure that we create Testability and
// it's ready on the window for users.
@ -68,7 +65,7 @@ function _injectorBindings(appComponentType): List<Binding> {
return componentRef;
});
}, [DynamicComponentLoader, Injector, DirectiveResolver,
}, [DynamicComponentLoader, Injector,
Testability, TestabilityRegistry]),
bind(appComponentType).toFactory((ref) => ref.instance,

View File

@ -62,19 +62,38 @@ export class DynamicComponentLoader {
}
/**
* Loads a component in the element specified by elementSelector. The loaded component receives
* injection normally as a hosted view.
* Loads a root component that is placed at the first element that matches the
* component's selector.
* The loaded component receives injection normally as a hosted view.
*/
loadIntoNewLocation(typeOrBinding, parentComponentLocation:ElementRef, elementSelector:string,
injector:Injector = null):Promise<ComponentRef> {
loadAsRoot(typeOrBinding, overrideSelector = null, injector:Injector = null):Promise<ComponentRef> {
return this._compiler.compileInHost(this._getBinding(typeOrBinding)).then(hostProtoViewRef => {
var hostViewRef = this._viewManager.createInPlaceHostView(
parentComponentLocation, elementSelector, hostProtoViewRef, injector);
var hostViewRef = this._viewManager.createRootHostView(hostProtoViewRef, overrideSelector, injector);
var newLocation = new ElementRef(hostViewRef, 0);
var component = this._viewManager.getComponent(newLocation);
var dispose = () => {
this._viewManager.destroyInPlaceHostView(parentComponentLocation, hostViewRef);
this._viewManager.destroyRootHostView(hostViewRef);
};
return new ComponentRef(newLocation, component, dispose);
});
}
/**
* Loads a component into a free host view that is not yet attached to
* a parent on the render side, although it is attached to a parent in the injector hierarchy.
* The loaded component receives injection normally as a hosted view.
*/
loadIntoNewLocation(typeOrBinding, parentComponentLocation:ElementRef,
injector:Injector = null):Promise<ComponentRef> {
return this._compiler.compileInHost(this._getBinding(typeOrBinding)).then(hostProtoViewRef => {
var hostViewRef = this._viewManager.createFreeHostView(
parentComponentLocation, hostProtoViewRef, injector);
var newLocation = new ElementRef(hostViewRef, 0);
var component = this._viewManager.getComponent(newLocation);
var dispose = () => {
this._viewManager.destroyFreeHostView(parentComponentLocation, hostViewRef);
};
return new ComponentRef(newLocation, component, dispose);
});

View File

@ -32,7 +32,7 @@ export class AppView {
componentChildViews: List<AppView>;
/// Host views that were added by an imperative view.
/// This is a dynamically growing / shrinking array.
inPlaceHostViews: List<AppView>;
freeHostViews: List<AppView>;
viewContainers: List<AppViewContainer>;
preBuiltObjects: List<PreBuiltObjects>;
proto: AppProtoView;
@ -64,7 +64,7 @@ export class AppView {
this.context = null;
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this
this.renderer = renderer;
this.inPlaceHostViews = [];
this.freeHostViews = [];
}
init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List,

View File

@ -62,33 +62,46 @@ export class AppViewManager {
return new ViewRef(componentView);
}
createInPlaceHostView(parentComponentLocation:ElementRef,
hostElementSelector:string, hostProtoViewRef:ProtoViewRef, injector:Injector):ViewRef {
createRootHostView(hostProtoViewRef:ProtoViewRef, overrideSelector:string, injector:Injector):ViewRef {
var hostProtoView = internalProtoView(hostProtoViewRef);
var parentComponentHostView = null;
var parentComponentBoundElementIndex = null;
var parentRenderViewRef = null;
if (isPresent(parentComponentLocation)) {
parentComponentHostView = internalView(parentComponentLocation.parentView);
parentComponentBoundElementIndex = parentComponentLocation.boundElementIndex;
parentRenderViewRef = parentComponentHostView.componentChildViews[parentComponentBoundElementIndex].render;
var hostElementSelector = overrideSelector;
if (isBlank(hostElementSelector)) {
hostElementSelector = hostProtoView.elementBinders[0].componentDirective.metadata.selector;
}
var hostRenderView = this._renderer.createInPlaceHostView(parentRenderViewRef, hostElementSelector, hostProtoView.render);
var hostView = this._utils.createView(hostProtoView, hostRenderView, this, this._renderer);
var renderView = this._renderer.createRootHostView(hostProtoView.render, hostElementSelector);
var hostView = this._utils.createView(hostProtoView, renderView, this, this._renderer);
this._renderer.setEventDispatcher(hostView.render, hostView);
this._createViewRecurse(hostView)
this._utils.attachAndHydrateInPlaceHostView(parentComponentHostView, parentComponentBoundElementIndex, hostView, injector);
this._createViewRecurse(hostView);
this._utils.hydrateRootHostView(hostView, injector);
this._viewHydrateRecurse(hostView);
return new ViewRef(hostView);
}
destroyInPlaceHostView(parentComponentLocation:ElementRef, hostViewRef:ViewRef) {
destroyRootHostView(hostViewRef:ViewRef) {
// Note: Don't detach the hostView as we want to leave the
// root element in place. Also don't put the hostView into the view pool
// as it is depending on the element for which it was created.
var hostView = internalView(hostViewRef);
var parentView = null;
if (isPresent(parentComponentLocation)) {
parentView = internalView(parentComponentLocation.parentView).componentChildViews[parentComponentLocation.boundElementIndex];
}
this._destroyInPlaceHostView(parentView, hostView);
// We do want to destroy the component view though.
this._viewDehydrateRecurse(hostView, true);
this._renderer.destroyView(hostView.render);
}
createFreeHostView(parentComponentLocation:ElementRef, hostProtoViewRef:ProtoViewRef, injector:Injector):ViewRef {
var hostProtoView = internalProtoView(hostProtoViewRef);
var hostView = this._createPooledView(hostProtoView);
var parentComponentHostView = internalView(parentComponentLocation.parentView);
var parentComponentBoundElementIndex = parentComponentLocation.boundElementIndex;
this._utils.attachAndHydrateFreeHostView(parentComponentHostView, parentComponentBoundElementIndex, hostView, injector);
this._viewHydrateRecurse(hostView);
return new ViewRef(hostView);
}
destroyFreeHostView(parentComponentLocation:ElementRef, hostViewRef:ViewRef) {
var hostView = internalView(hostViewRef);
var parentView = internalView(parentComponentLocation.parentView).componentChildViews[parentComponentLocation.boundElementIndex];
this._destroyFreeHostView(parentView, hostView);
}
createViewInContainer(viewContainerLocation:ElementRef,
@ -186,16 +199,11 @@ export class AppViewManager {
this._destroyPooledView(componentView);
}
_destroyInPlaceHostView(parentView, hostView) {
var parentRenderViewRef = null;
if (isPresent(parentView)) {
parentRenderViewRef = parentView.render;
}
_destroyFreeHostView(parentView, hostView) {
this._viewDehydrateRecurse(hostView, true);
this._utils.detachInPlaceHostView(parentView, hostView);
this._renderer.destroyInPlaceHostView(parentRenderViewRef, hostView.render);
// Note: Don't put the inplace host view into the view pool
// as it is depending on the element for which it was created.
this._renderer.detachFreeHostView(parentView.render, hostView.render);
this._utils.detachFreeHostView(parentView, hostView);
this._destroyPooledView(hostView);
}
_viewHydrateRecurse(
@ -234,10 +242,10 @@ export class AppViewManager {
}
}
// inPlaceHostViews
for (var i = view.inPlaceHostViews.length-1; i>=0; i--) {
var hostView = view.inPlaceHostViews[i];
this._destroyInPlaceHostView(view, hostView);
// freeHostViews
for (var i = view.freeHostViews.length-1; i>=0; i--) {
var hostView = view.freeHostViews[i];
this._destroyFreeHostView(view, hostView);
}
}
}

View File

@ -93,24 +93,23 @@ export class AppViewManagerUtils {
);
}
attachAndHydrateInPlaceHostView(parentComponentHostView:viewModule.AppView, parentComponentBoundElementIndex:number,
hydrateRootHostView(hostView:viewModule.AppView, injector:Injector = null) {
this._hydrateView(hostView, injector, null, new Object(), null);
}
attachAndHydrateFreeHostView(parentComponentHostView:viewModule.AppView, parentComponentBoundElementIndex:number,
hostView:viewModule.AppView, injector:Injector = null) {
var hostElementInjector = null;
if (isPresent(parentComponentHostView)) {
hostElementInjector = parentComponentHostView.elementInjectors[parentComponentBoundElementIndex];
var parentView = parentComponentHostView.componentChildViews[parentComponentBoundElementIndex];
parentView.changeDetector.addChild(hostView.changeDetector);
ListWrapper.push(parentView.inPlaceHostViews, hostView);
}
var hostElementInjector = parentComponentHostView.elementInjectors[parentComponentBoundElementIndex];
var parentView = parentComponentHostView.componentChildViews[parentComponentBoundElementIndex];
parentView.changeDetector.addChild(hostView.changeDetector);
ListWrapper.push(parentView.freeHostViews, hostView);
this._hydrateView(hostView, injector, hostElementInjector, new Object(), null);
}
detachInPlaceHostView(parentView:viewModule.AppView,
detachFreeHostView(parentView:viewModule.AppView,
hostView:viewModule.AppView) {
if (isPresent(parentView)) {
parentView.changeDetector.removeChild(hostView.changeDetector);
ListWrapper.remove(parentView.inPlaceHostViews, hostView);
}
parentView.changeDetector.removeChild(hostView.changeDetector);
ListWrapper.remove(parentView.freeHostViews, hostView);
}
attachViewInContainer(parentView:viewModule.AppView, boundElementIndex:number,