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:
9
modules/angular2/src/core/application.js
vendored
9
modules/angular2/src/core/application.js
vendored
@ -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,
|
||||
|
@ -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);
|
||||
});
|
||||
|
4
modules/angular2/src/core/compiler/view.js
vendored
4
modules/angular2/src/core/compiler/view.js
vendored
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
11
modules/angular2/src/render/api.js
vendored
11
modules/angular2/src/render/api.js
vendored
@ -187,20 +187,19 @@ export class RenderCompiler {
|
||||
|
||||
export class Renderer {
|
||||
/**
|
||||
* Creates a host view that includes the given element.
|
||||
* @param {RenderViewRef} parentHostViewRef (might be null)
|
||||
* @param {any} hostElementSelector css selector for the host element
|
||||
* Creates a root host view that includes the given element.
|
||||
* @param {RenderProtoViewRef} hostProtoViewRef a RenderProtoViewRef of type ProtoViewDto.HOST_VIEW_TYPE
|
||||
* @param {any} hostElementSelector css selector for the host element (will be queried against the main document)
|
||||
* @return {RenderViewRef} the created view
|
||||
*/
|
||||
createInPlaceHostView(parentHostViewRef:RenderViewRef, hostElementSelector:string, hostProtoViewRef:RenderProtoViewRef):RenderViewRef {
|
||||
createRootHostView(hostProtoViewRef:RenderProtoViewRef, hostElementSelector:string):RenderViewRef {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the given host view in the given parent view.
|
||||
* Detaches a free host view's element from the DOM.
|
||||
*/
|
||||
destroyInPlaceHostView(parentHostViewRef:RenderViewRef, hostViewRef:RenderViewRef) {
|
||||
detachFreeHostView(parentHostViewRef:RenderViewRef, hostViewRef:RenderViewRef) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
26
modules/angular2/src/render/dom/dom_renderer.js
vendored
26
modules/angular2/src/render/dom/dom_renderer.js
vendored
@ -19,8 +19,6 @@ import {Renderer, RenderProtoViewRef, RenderViewRef} from '../api';
|
||||
// const expressions!
|
||||
export const DOCUMENT_TOKEN = 'DocumentToken';
|
||||
|
||||
var _DOCUMENT_SELECTOR_REGEX = RegExpWrapper.create('\\:document(.+)');
|
||||
|
||||
@Injectable()
|
||||
export class DomRenderer extends Renderer {
|
||||
_eventManager:EventManager;
|
||||
@ -34,27 +32,16 @@ export class DomRenderer extends Renderer {
|
||||
this._document = document;
|
||||
}
|
||||
|
||||
createInPlaceHostView(parentHostViewRef:RenderViewRef, hostElementSelector:string, hostProtoViewRef:RenderProtoViewRef):RenderViewRef {
|
||||
var containerNode;
|
||||
var documentSelectorMatch = RegExpWrapper.firstMatch(_DOCUMENT_SELECTOR_REGEX, hostElementSelector);
|
||||
if (isPresent(documentSelectorMatch)) {
|
||||
containerNode = this._document;
|
||||
hostElementSelector = documentSelectorMatch[1];
|
||||
} else if (isPresent(parentHostViewRef)) {
|
||||
var parentHostView = resolveInternalDomView(parentHostViewRef);
|
||||
containerNode = parentHostView.shadowRoot;
|
||||
} else {
|
||||
containerNode = this._document;
|
||||
}
|
||||
var element = DOM.querySelector(containerNode, hostElementSelector);
|
||||
createRootHostView(hostProtoViewRef:RenderProtoViewRef, hostElementSelector:string):RenderViewRef {
|
||||
var hostProtoView = resolveInternalDomProtoView(hostProtoViewRef);
|
||||
var element = DOM.querySelector(this._document, hostElementSelector);
|
||||
if (isBlank(element)) {
|
||||
throw new BaseException(`The selector "${hostElementSelector}" did not match any elements`);
|
||||
}
|
||||
var hostProtoView = resolveInternalDomProtoView(hostProtoViewRef);
|
||||
return new DomViewRef(this._createView(hostProtoView, element));
|
||||
}
|
||||
|
||||
destroyInPlaceHostView(parentHostViewRef:RenderViewRef, hostViewRef:RenderViewRef) {
|
||||
detachFreeHostView(parentHostViewRef:RenderViewRef, hostViewRef:RenderViewRef) {
|
||||
var hostView = resolveInternalDomView(hostViewRef);
|
||||
this._removeViewNodes(hostView);
|
||||
}
|
||||
@ -89,6 +76,11 @@ export class DomRenderer extends Renderer {
|
||||
this._moveViewNodesIntoParent(componentView.shadowRoot, componentView);
|
||||
}
|
||||
|
||||
getHostElement(hostViewRef:RenderViewRef) {
|
||||
var hostView = resolveInternalDomView(hostViewRef);
|
||||
return hostView.boundElements[0];
|
||||
}
|
||||
|
||||
detachComponentView(hostViewRef:RenderViewRef, boundElementIndex:number, componentViewRef:RenderViewRef) {
|
||||
var hostView = resolveInternalDomView(hostViewRef);
|
||||
var componentView = resolveInternalDomView(componentViewRef);
|
||||
|
2
modules/angular2/src/test_lib/test_bed.js
vendored
2
modules/angular2/src/test_lib/test_bed.js
vendored
@ -94,7 +94,7 @@ export class TestBed {
|
||||
DOM.appendChild(doc.body, rootEl);
|
||||
|
||||
var componentBinding = bind(component).toValue(context);
|
||||
return this._injector.get(DynamicComponentLoader).loadIntoNewLocation(componentBinding, null, '#root', this._injector).then((hostComponentRef) => {
|
||||
return this._injector.get(DynamicComponentLoader).loadAsRoot(componentBinding,'#root', this._injector).then((hostComponentRef) => {
|
||||
return new ViewProxy(hostComponentRef);
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user