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:
parent
a38a0d6f87
commit
421d8916a6
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,17 +11,18 @@ import {
|
|||
inject,
|
||||
beforeEachBindings,
|
||||
it,
|
||||
xit
|
||||
xit,
|
||||
viewRootNodes
|
||||
} from 'angular2/test_lib';
|
||||
|
||||
import {TestBed} from 'angular2/src/test_lib/test_bed';
|
||||
|
||||
import {TestBed, ViewProxy} from 'angular2/src/test_lib/test_bed';
|
||||
import {Injector} from 'angular2/di';
|
||||
import {Component} from 'angular2/src/core/annotations_impl/annotations';
|
||||
import {View} from 'angular2/src/core/annotations_impl/view';
|
||||
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
|
||||
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
|
||||
import {NgIf} from 'angular2/src/directives/ng_if';
|
||||
import {DomRenderer} from 'angular2/src/render/dom/dom_renderer';
|
||||
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
|
||||
|
||||
|
@ -193,6 +194,37 @@ export function main() {
|
|||
|
||||
});
|
||||
|
||||
describe('loadAsRoot', () => {
|
||||
|
||||
it('should allow to create, update and destroy components',
|
||||
inject([TestBed, AsyncTestCompleter, DynamicComponentLoader, DOCUMENT_TOKEN, Injector], (tb, async, dcl, doc, injector) => {
|
||||
var rootEl = el('<child-cmp></child-cmp>');
|
||||
DOM.appendChild(doc.body, rootEl);
|
||||
dcl.loadAsRoot(ChildComp, null, injector).then( (componentRef) => {
|
||||
var view = new ViewProxy(componentRef);
|
||||
expect(rootEl.parentNode).toBe(doc.body);
|
||||
|
||||
view.detectChanges();
|
||||
|
||||
expect(rootEl).toHaveText('hello');
|
||||
|
||||
componentRef.instance.ctxProp = 'new';
|
||||
|
||||
view.detectChanges();
|
||||
|
||||
expect(rootEl).toHaveText('new');
|
||||
|
||||
componentRef.dispose();
|
||||
|
||||
expect(rootEl).toHaveText('');
|
||||
expect(rootEl.parentNode).toBe(doc.body);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -200,7 +232,6 @@ export function main() {
|
|||
selector: 'imp-ng-cmp'
|
||||
})
|
||||
@View({
|
||||
renderer: 'imp-ng-cmp-renderer',
|
||||
template: ''
|
||||
})
|
||||
class ImperativeViewComponentUsingNgComponent {
|
||||
|
@ -210,7 +241,11 @@ class ImperativeViewComponentUsingNgComponent {
|
|||
var div = el('<div id="impHost"></div>');
|
||||
var shadowViewRef = viewManager.getComponentView(self);
|
||||
renderer.setComponentViewRootNodes(shadowViewRef.render, [div]);
|
||||
this.done = dynamicComponentLoader.loadIntoNewLocation(ChildComp, self, '#impHost', null);
|
||||
this.done = dynamicComponentLoader.loadIntoNewLocation(ChildComp, self, null).then( (componentRef) => {
|
||||
var element = renderer.getHostElement(componentRef.hostView.render);
|
||||
DOM.appendChild(div, element);
|
||||
return componentRef;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ export function main() {
|
|||
}
|
||||
ListWrapper.insert(viewContainer.views, atIndex, childView);
|
||||
});
|
||||
renderer.spy('createInPlaceHostView').andCallFake( (_a, _b, _c) => {
|
||||
renderer.spy('createRootHostView').andCallFake( (_b, _c) => {
|
||||
var rv = new RenderViewRef();
|
||||
ListWrapper.push(createdRenderViews, rv);
|
||||
return rv;
|
||||
|
@ -294,7 +294,7 @@ export function main() {
|
|||
|
||||
});
|
||||
|
||||
describe('createInPlaceHostView', () => {
|
||||
describe('createFreeHostView', () => {
|
||||
|
||||
// Note: We don't add tests for recursion or viewpool here as we assume that
|
||||
// this is using the same mechanism as the other methods...
|
||||
|
@ -314,27 +314,26 @@ export function main() {
|
|||
|
||||
it('should create the view', () => {
|
||||
expect(
|
||||
internalView(manager.createInPlaceHostView(elementRef(wrapView(parentHostView), 0), null, wrapPv(hostProtoView), null))
|
||||
internalView(manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), null))
|
||||
).toBe(createdViews[0]);
|
||||
expect(createdViews[0].proto).toBe(hostProtoView);
|
||||
});
|
||||
|
||||
it('should attachAndHydrate the view', () => {
|
||||
var injector = new Injector([], null, false);
|
||||
manager.createInPlaceHostView(elementRef(wrapView(parentHostView), 0), null, wrapPv(hostProtoView), injector);
|
||||
expect(utils.spy('attachAndHydrateInPlaceHostView')).toHaveBeenCalledWith(parentHostView, 0, createdViews[0], injector);
|
||||
manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), injector);
|
||||
expect(utils.spy('attachAndHydrateFreeHostView')).toHaveBeenCalledWith(parentHostView, 0, createdViews[0], injector);
|
||||
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
|
||||
});
|
||||
|
||||
it('should create and set the render view', () => {
|
||||
var elementOrSelector = 'someSelector';
|
||||
manager.createInPlaceHostView(elementRef(wrapView(parentHostView), 0), elementOrSelector, wrapPv(hostProtoView), null)
|
||||
expect(renderer.spy('createInPlaceHostView')).toHaveBeenCalledWith(parentView.render, elementOrSelector, hostProtoView.render);
|
||||
manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), null)
|
||||
expect(renderer.spy('createView')).toHaveBeenCalledWith(hostProtoView.render);
|
||||
expect(createdViews[0].render).toBe(createdRenderViews[0]);
|
||||
});
|
||||
|
||||
it('should set the event dispatcher', () => {
|
||||
manager.createInPlaceHostView(elementRef(wrapView(parentHostView), 0), null, wrapPv(hostProtoView), null);
|
||||
manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), null);
|
||||
var cmpView = createdViews[0];
|
||||
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView);
|
||||
});
|
||||
|
@ -343,7 +342,7 @@ export function main() {
|
|||
});
|
||||
|
||||
|
||||
describe('destroyInPlaceHostView', () => {
|
||||
describe('destroyFreeHostView', () => {
|
||||
describe('basic functionality', () => {
|
||||
var parentHostView, parentView, hostProtoView, hostView, hostRenderViewRef;
|
||||
beforeEach( () => {
|
||||
|
@ -355,29 +354,29 @@ export function main() {
|
|||
hostProtoView = createProtoView(
|
||||
[createComponentElBinder(null)]
|
||||
);
|
||||
hostView = internalView(manager.createInPlaceHostView(elementRef(wrapView(parentHostView), 0), null, wrapPv(hostProtoView), null));
|
||||
hostView = internalView(manager.createFreeHostView(elementRef(wrapView(parentHostView), 0), wrapPv(hostProtoView), null));
|
||||
hostRenderViewRef = hostView.render;
|
||||
});
|
||||
|
||||
it('should detach', () => {
|
||||
manager.destroyInPlaceHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
|
||||
expect(utils.spy('detachInPlaceHostView')).toHaveBeenCalledWith(parentView, hostView);
|
||||
manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
|
||||
expect(utils.spy('detachFreeHostView')).toHaveBeenCalledWith(parentView, hostView);
|
||||
});
|
||||
|
||||
it('should dehydrate', () => {
|
||||
manager.destroyInPlaceHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
|
||||
manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
|
||||
expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(hostView);
|
||||
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render);
|
||||
});
|
||||
|
||||
it('should destroy and clear the render view', () => {
|
||||
manager.destroyInPlaceHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
|
||||
expect(renderer.spy('destroyInPlaceHostView')).toHaveBeenCalledWith(parentView.render, hostRenderViewRef);
|
||||
it('should detach the render view', () => {
|
||||
manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
|
||||
expect(renderer.spy('detachFreeHostView')).toHaveBeenCalledWith(parentView.render, hostRenderViewRef);
|
||||
});
|
||||
|
||||
it('should not return the view to the pool', () => {
|
||||
manager.destroyInPlaceHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
|
||||
expect(viewPool.spy('returnView')).not.toHaveBeenCalled();
|
||||
it('should return the view to the pool', () => {
|
||||
manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
|
||||
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(hostView);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -387,6 +386,78 @@ export function main() {
|
|||
|
||||
});
|
||||
|
||||
describe('createRootHostView', () => {
|
||||
|
||||
var hostProtoView;
|
||||
beforeEach( () => {
|
||||
hostProtoView = createProtoView(
|
||||
[createComponentElBinder(null)]
|
||||
);
|
||||
});
|
||||
|
||||
it('should create the view', () => {
|
||||
expect(
|
||||
internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null))
|
||||
).toBe(createdViews[0]);
|
||||
expect(createdViews[0].proto).toBe(hostProtoView);
|
||||
});
|
||||
|
||||
it('should hydrate the view', () => {
|
||||
var injector = new Injector([], null, false);
|
||||
manager.createRootHostView(wrapPv(hostProtoView), null, injector);
|
||||
expect(utils.spy('hydrateRootHostView')).toHaveBeenCalledWith(createdViews[0], injector);
|
||||
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
|
||||
});
|
||||
|
||||
it('should create and set the render view using the component selector', () => {
|
||||
manager.createRootHostView(wrapPv(hostProtoView), null, null)
|
||||
expect(renderer.spy('createRootHostView')).toHaveBeenCalledWith(hostProtoView.render, 'someComponent');
|
||||
expect(createdViews[0].render).toBe(createdRenderViews[0]);
|
||||
});
|
||||
|
||||
it('should allow to override the selector', () => {
|
||||
var selector = 'someOtherSelector';
|
||||
manager.createRootHostView(wrapPv(hostProtoView), selector, null)
|
||||
expect(renderer.spy('createRootHostView')).toHaveBeenCalledWith(hostProtoView.render, selector);
|
||||
});
|
||||
|
||||
it('should set the event dispatcher', () => {
|
||||
manager.createRootHostView(wrapPv(hostProtoView), null, null);
|
||||
var cmpView = createdViews[0];
|
||||
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('destroyRootHostView', () => {
|
||||
var hostProtoView, hostView, hostRenderViewRef;
|
||||
beforeEach( () => {
|
||||
hostProtoView = createProtoView(
|
||||
[createComponentElBinder(null)]
|
||||
);
|
||||
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
|
||||
hostRenderViewRef = hostView.render;
|
||||
});
|
||||
|
||||
it('should dehydrate', () => {
|
||||
manager.destroyRootHostView(wrapView(hostView));
|
||||
expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(hostView);
|
||||
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render);
|
||||
});
|
||||
|
||||
it('should destroy the render view', () => {
|
||||
manager.destroyRootHostView(wrapView(hostView));
|
||||
expect(renderer.spy('destroyView')).toHaveBeenCalledWith(hostRenderViewRef);
|
||||
});
|
||||
|
||||
it('should not return the view to the pool', () => {
|
||||
manager.destroyRootHostView(wrapView(hostView));
|
||||
expect(viewPool.spy('returnView')).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('createViewInContainer', () => {
|
||||
|
||||
describe('basic functionality', () => {
|
||||
|
@ -483,19 +554,19 @@ export function main() {
|
|||
});
|
||||
|
||||
it('should dehydrate', () => {
|
||||
manager.destroyInPlaceHostView(null, wrapView(parentView));
|
||||
manager.destroyRootHostView(wrapView(parentView));
|
||||
expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(parentView.viewContainers[0].views[0]);
|
||||
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(childView.render);
|
||||
});
|
||||
|
||||
it('should detach', () => {
|
||||
manager.destroyInPlaceHostView(null, wrapView(parentView));
|
||||
manager.destroyRootHostView(wrapView(parentView));
|
||||
expect(utils.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0);
|
||||
expect(renderer.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView.render, 0, 0, childView.render);
|
||||
});
|
||||
|
||||
it('should return the view to the pool', () => {
|
||||
manager.destroyInPlaceHostView(null, wrapView(parentView));
|
||||
manager.destroyRootHostView(wrapView(parentView));
|
||||
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(childView);
|
||||
});
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils
|
|||
export function main() {
|
||||
// TODO(tbosch): add more tests here!
|
||||
|
||||
|
||||
describe('AppViewManagerUtils', () => {
|
||||
|
||||
var directiveResolver;
|
||||
|
@ -170,7 +169,7 @@ export function main() {
|
|||
var shadowView = createView();
|
||||
utils.attachComponentView(hostView, 0, shadowView);
|
||||
|
||||
utils.attachAndHydrateInPlaceHostView(null, null, hostView, createInjector());
|
||||
utils.hydrateRootHostView(hostView, createInjector());
|
||||
|
||||
expect(spyEventAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir);
|
||||
expect(spyEventAccessor2.spy('subscribe')).toHaveBeenCalledWith(hostView, 1, dir);
|
||||
|
@ -200,7 +199,7 @@ export function main() {
|
|||
var shadowView = createView();
|
||||
utils.attachComponentView(hostView, 0, shadowView);
|
||||
|
||||
utils.attachAndHydrateInPlaceHostView(null, null, hostView, createInjector());
|
||||
utils.hydrateRootHostView(hostView, createInjector());
|
||||
|
||||
expect(spyActionAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir);
|
||||
expect(spyActionAccessor2.spy('subscribe')).toHaveBeenCalledWith(hostView, 1, dir);
|
||||
|
@ -268,6 +267,27 @@ export function main() {
|
|||
|
||||
});
|
||||
|
||||
describe('hydrateRootHostView', () => {
|
||||
var hostView;
|
||||
|
||||
function createViews() {
|
||||
var hostPv = createProtoView([
|
||||
createComponentElBinder()
|
||||
]);
|
||||
hostView = createView(hostPv);
|
||||
}
|
||||
|
||||
it("should instantiate the elementInjectors with the given injector and an empty host element injector", () => {
|
||||
var injector = createInjector();
|
||||
createViews();
|
||||
|
||||
utils.hydrateRootHostView(hostView, injector);
|
||||
expect(hostView.rootElementInjectors[0].spy('instantiateDirectives'))
|
||||
.toHaveBeenCalledWith(injector, null, hostView.preBuiltObjects[0]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
import {MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {DomTestbed} from './dom_testbed';
|
||||
import {DomTestbed, TestView} from './dom_testbed';
|
||||
|
||||
import {ViewDefinition, DirectiveMetadata, RenderViewRef} from 'angular2/src/render/api';
|
||||
|
||||
|
@ -27,15 +27,29 @@ export function main() {
|
|||
DomTestbed
|
||||
]);
|
||||
|
||||
it('should create and destroy host views while using the given elements in place',
|
||||
it('should create and destroy root host views while using the given elements in place',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([someComponent]).then( (protoViewDtos) => {
|
||||
var view = tb.createRootView(protoViewDtos[0]);
|
||||
expect(tb.rootEl.parentNode).toBeTruthy();
|
||||
tb.compiler.compileHost(someComponent).then( (hostProtoViewDto) => {
|
||||
var view = new TestView(tb.renderer.createRootHostView(hostProtoViewDto.render, '#root'));
|
||||
expect(view.rawView.rootNodes[0]).toEqual(tb.rootEl);
|
||||
|
||||
tb.renderer.destroyInPlaceHostView(null, view.viewRef);
|
||||
expect(tb.rootEl.parentNode).toBeFalsy();
|
||||
tb.renderer.destroyView(view.viewRef);
|
||||
// destroying a root view should not disconnect it!
|
||||
expect(tb.rootEl.parentNode).toBeTruthy();
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should create and destroy free host views',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compiler.compileHost(someComponent).then( (hostProtoViewDto) => {
|
||||
var view = new TestView(tb.renderer.createView(hostProtoViewDto.render));
|
||||
var hostElement = tb.renderer.getHostElement(view.viewRef);
|
||||
DOM.appendChild(tb.rootEl, hostElement);
|
||||
|
||||
tb.renderer.detachFreeHostView(null, view.viewRef);
|
||||
expect(DOM.parentElement(hostElement)).toBeFalsy();
|
||||
|
||||
async.done();
|
||||
});
|
||||
|
|
|
@ -77,7 +77,7 @@ export class DomTestbed {
|
|||
}
|
||||
|
||||
createRootView(rootProtoView:ProtoViewDto):TestView {
|
||||
var viewRef = this.renderer.createInPlaceHostView(null, '#root', rootProtoView.render);
|
||||
var viewRef = this.renderer.createRootHostView(rootProtoView.render, '#root');
|
||||
this.renderer.hydrateView(viewRef);
|
||||
return this._createTestView(viewRef);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {DynamicComponentLoader, ElementRef, ComponentRef, onDestroy} from 'angular2/angular2';
|
||||
import {DynamicComponentLoader, ElementRef, ComponentRef, onDestroy, DomRenderer} from 'angular2/angular2';
|
||||
import {bind, Injector} from 'angular2/di';
|
||||
import {ObservableWrapper, Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {isPresent, Type} from 'angular2/src/facade/lang';
|
||||
|
@ -12,7 +12,6 @@ import {Component, Directive} from 'angular2/src/core/annotations_impl/annotatio
|
|||
import {Parent} from 'angular2/src/core/annotations_impl/visibility';
|
||||
import {View} from 'angular2/src/core/annotations_impl/view';
|
||||
|
||||
|
||||
// TODO(jelbourn): Opener of dialog can control where it is rendered.
|
||||
// TODO(jelbourn): body scrolling is disabled while dialog is open.
|
||||
// TODO(jelbourn): Don't manually construct and configure a DOM element. See #1402
|
||||
|
@ -29,9 +28,11 @@ var _nextDialogId = 0;
|
|||
*/
|
||||
export class MdDialog {
|
||||
componentLoader: DynamicComponentLoader;
|
||||
domRenderer: DomRenderer;
|
||||
|
||||
constructor(loader: DynamicComponentLoader) {
|
||||
constructor(loader: DynamicComponentLoader, domRenderer: DomRenderer) {
|
||||
this.componentLoader = loader;
|
||||
this.domRenderer = domRenderer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,25 +48,6 @@ export class MdDialog {
|
|||
options: MdDialogConfig = null): Promise<MdDialogRef> {
|
||||
var config = isPresent(options) ? options : new MdDialogConfig();
|
||||
|
||||
// TODO(jelbourn): Don't use direct DOM access. Need abstraction to create an element
|
||||
// directly on the document body (also needed for web workers stuff).
|
||||
// Create a DOM node to serve as a physical host element for the dialog.
|
||||
var dialogElement = this._createHostElement();
|
||||
DOM.appendChild(DOM.query('body'), dialogElement);
|
||||
|
||||
// TODO(jelbourn): Use hostProperties binding to set these once #1539 is fixed.
|
||||
// Configure properties on the host element.
|
||||
DOM.addClass(dialogElement, 'md-dialog');
|
||||
DOM.setAttribute(dialogElement, 'tabindex', '0');
|
||||
|
||||
// TODO(jelbourn): Do this with hostProperties (or another rendering abstraction) once ready.
|
||||
if (isPresent(config.width)) {
|
||||
DOM.setStyle(dialogElement, 'width', config.width);
|
||||
}
|
||||
if (isPresent(config.height)) {
|
||||
DOM.setStyle(dialogElement, 'height', config.height);
|
||||
}
|
||||
|
||||
// Create the dialogRef here so that it can be injected into the content component.
|
||||
var dialogRef = new MdDialogRef();
|
||||
|
||||
|
@ -76,7 +58,27 @@ export class MdDialog {
|
|||
|
||||
// First, load the MdDialogContainer, into which the given component will be loaded.
|
||||
return this.componentLoader.loadIntoNewLocation(
|
||||
MdDialogContainer, elementRef, `:document#${dialogElement.id}`).then(containerRef => {
|
||||
MdDialogContainer, elementRef).then(containerRef => {
|
||||
// TODO(tbosch): clean this up when we have custom renderers (https://github.com/angular/angular/issues/1807)
|
||||
// TODO(jelbourn): Don't use direct DOM access. Need abstraction to create an element
|
||||
// directly on the document body (also needed for web workers stuff).
|
||||
// Create a DOM node to serve as a physical host element for the dialog.
|
||||
var dialogElement = this.domRenderer.getHostElement(containerRef.hostView.render);
|
||||
DOM.appendChild(DOM.query('body'), dialogElement);
|
||||
|
||||
// TODO(jelbourn): Use hostProperties binding to set these once #1539 is fixed.
|
||||
// Configure properties on the host element.
|
||||
DOM.addClass(dialogElement, 'md-dialog');
|
||||
DOM.setAttribute(dialogElement, 'tabindex', '0');
|
||||
|
||||
// TODO(jelbourn): Do this with hostProperties (or another rendering abstraction) once ready.
|
||||
if (isPresent(config.width)) {
|
||||
DOM.setStyle(dialogElement, 'width', config.width);
|
||||
}
|
||||
if (isPresent(config.height)) {
|
||||
DOM.setStyle(dialogElement, 'height', config.height);
|
||||
}
|
||||
|
||||
dialogRef.containerRef = containerRef;
|
||||
|
||||
// Now load the given component into the MdDialogContainer.
|
||||
|
@ -102,18 +104,14 @@ export class MdDialog {
|
|||
|
||||
/** Loads the dialog backdrop (transparent overlay over the rest of the page). */
|
||||
_openBackdrop(elementRef:ElementRef, injector: Injector): Promise<ComponentRef> {
|
||||
var backdropElement = this._createHostElement();
|
||||
DOM.addClass(backdropElement, 'md-backdrop');
|
||||
DOM.appendChild(DOM.query('body'), backdropElement);
|
||||
|
||||
return this.componentLoader.loadIntoNewLocation(
|
||||
MdBackdrop, elementRef, `:document#${backdropElement.id}`, injector);
|
||||
}
|
||||
|
||||
_createHostElement() {
|
||||
var hostElement = DOM.createElement('div');
|
||||
hostElement.id = `mdDialog${_nextDialogId++}`;
|
||||
return hostElement;
|
||||
MdBackdrop, elementRef, injector).then( (componentRef) => {
|
||||
// TODO(tbosch): clean this up when we have custom renderers (https://github.com/angular/angular/issues/1807)
|
||||
var backdropElement = this.domRenderer.getHostElement(componentRef.hostView.render);
|
||||
DOM.addClass(backdropElement, 'md-backdrop');
|
||||
DOM.appendChild(DOM.query('body'), backdropElement);
|
||||
return componentRef;
|
||||
});
|
||||
}
|
||||
|
||||
alert(message: string, okMessage: string): Promise {
|
||||
|
|
Loading…
Reference in New Issue