feat(view): add imperative views

This commit is contained in:
Tobias Bosch 2015-04-20 11:34:53 -07:00
parent 817c79ca77
commit ada1e642c5
28 changed files with 458 additions and 120 deletions

View File

@ -4,3 +4,5 @@ export * from './annotations';
export * from './directives'; export * from './directives';
export * from './forms'; export * from './forms';
export {Observable, EventEmitter} from 'angular2/src/facade/async'; export {Observable, EventEmitter} from 'angular2/src/facade/async';
export * from 'angular2/src/render/api';
export {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer';

View File

@ -71,19 +71,28 @@ export class View {
*/ */
directives:any; //List<Type>; directives:any; //List<Type>;
/**
* Specify a custom renderer for this View.
* If this is set, neither `template`, `templateURL` nor `directives` are used.
*/
renderer:any; // string;
@CONST() @CONST()
constructor({ constructor({
templateUrl, templateUrl,
template, template,
directives directives,
renderer
}: { }: {
templateUrl: string, templateUrl: string,
template: string, template: string,
directives: List<Type> directives: List<Type>,
renderer: string
}) })
{ {
this.templateUrl = templateUrl; this.templateUrl = templateUrl;
this.template = template; this.template = template;
this.directives = directives; this.directives = directives;
this.renderer = renderer;
} }
} }

View File

@ -75,7 +75,7 @@ function _injectorBindings(appComponentType): List<Binding> {
// We need to do this here to ensure that we create Testability and // We need to do this here to ensure that we create Testability and
// it's ready on the window for users. // it's ready on the window for users.
registry.registerApplication(appElement, testability); registry.registerApplication(appElement, testability);
return dynamicComponentLoader.loadIntoNewLocation(appElement, appComponentAnnotatedType.type, injector); return dynamicComponentLoader.loadIntoNewLocation(appComponentAnnotatedType.type, null, appElement, injector);
}, [DynamicComponentLoader, Injector, appElementToken, appComponentAnnotatedTypeToken, }, [DynamicComponentLoader, Injector, appElementToken, appComponentAnnotatedTypeToken,
Testability, TestabilityRegistry]), Testability, TestabilityRegistry]),
@ -91,6 +91,7 @@ function _injectorBindings(appComponentType): List<Binding> {
bind(ShadowDomStrategy).toFactory( bind(ShadowDomStrategy).toFactory(
(styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head), (styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head),
[StyleUrlResolver, appDocumentToken]), [StyleUrlResolver, appDocumentToken]),
DirectDomRenderer,
bind(Renderer).toClass(DirectDomRenderer), bind(Renderer).toClass(DirectDomRenderer),
bind(rc.Compiler).toClass(rc.DefaultCompiler), bind(rc.Compiler).toClass(rc.DefaultCompiler),
// TODO(tbosch): We need an explicit factory here, as // TODO(tbosch): We need an explicit factory here, as
@ -105,8 +106,8 @@ function _injectorBindings(appComponentType): List<Binding> {
// TODO(tbosch): We need an explicit factory here, as // TODO(tbosch): We need an explicit factory here, as
// we are getting errors in dart2js with mirrors... // we are getting errors in dart2js with mirrors...
bind(ViewFactory).toFactory( bind(ViewFactory).toFactory(
(capacity, renderer, appViewHydrator) => new ViewFactory(capacity, renderer, appViewHydrator), (capacity, renderer) => new ViewFactory(capacity, renderer),
[VIEW_POOL_CAPACITY, Renderer, AppViewHydrator] [VIEW_POOL_CAPACITY, Renderer]
), ),
bind(VIEW_POOL_CAPACITY).toValue(10000), bind(VIEW_POOL_CAPACITY).toValue(10000),
AppViewHydrator, AppViewHydrator,

View File

@ -114,6 +114,12 @@ export class Compiler {
} }
var template = this._templateResolver.resolve(component); var template = this._templateResolver.resolve(component);
if (isPresent(template.renderer)) {
var directives = [];
pvPromise = this._renderer.createImperativeComponentProtoView(template.renderer).then( (renderPv) => {
return this._compileNestedProtoViews(componentBinding, renderPv, directives, true);
});
} else {
var directives = ListWrapper.map( var directives = ListWrapper.map(
this._flattenDirectives(template), this._flattenDirectives(template),
(directive) => this._bindDirective(directive) (directive) => this._bindDirective(directive)
@ -122,6 +128,7 @@ export class Compiler {
pvPromise = this._renderer.compile(renderTemplate).then( (renderPv) => { pvPromise = this._renderer.compile(renderTemplate).then( (renderPv) => {
return this._compileNestedProtoViews(componentBinding, renderPv, directives, true); return this._compileNestedProtoViews(componentBinding, renderPv, directives, true);
}); });
}
MapWrapper.set(this._compiling, component, pvPromise); MapWrapper.set(this._compiling, component, pvPromise);
return pvPromise; return pvPromise;

View File

@ -70,15 +70,10 @@ export class DynamicComponentLoader {
return this._compiler.compile(type).then(componentProtoView => { return this._compiler.compile(type).then(componentProtoView => {
var componentView = this._viewFactory.getView(componentProtoView); var componentView = this._viewFactory.getView(componentProtoView);
var hostView = location.hostView;
this._viewHydrator.hydrateDynamicComponentView( this._viewHydrator.hydrateDynamicComponentView(
hostView, location.boundElementIndex, componentView, componentBinding, injector); location, componentView, componentBinding, injector);
// TODO(vsavkin): return a component ref that dehydrates the component view and removes it var dispose = () => {throw new BaseException("Not implemented");};
// from the component child views
// See ViewFactory.returnView
// See AppViewHydrator.dehydrateDynamicComponentView
var dispose = () => {throw "Not implemented";};
return new ComponentRef(location, location.elementInjector.getDynamicallyLoadedComponent(), componentView, dispose); return new ComponentRef(location, location.elementInjector.getDynamicallyLoadedComponent(), componentView, dispose);
}); });
} }
@ -87,19 +82,22 @@ export class DynamicComponentLoader {
* Loads a component in the element specified by elementOrSelector. The loaded component receives * Loads a component in the element specified by elementOrSelector. The loaded component receives
* injection normally as a hosted view. * injection normally as a hosted view.
*/ */
loadIntoNewLocation(elementOrSelector:any, type:Type, injector:Injector = null):Promise<ComponentRef> { loadIntoNewLocation(type:Type, parentComponentLocation:ElementRef, elementOrSelector:any,
injector:Injector = null):Promise<ComponentRef> {
this._assertTypeIsComponent(type); this._assertTypeIsComponent(type);
return this._compiler.compileInHost(type).then(hostProtoView => { return this._compiler.compileInHost(type).then(hostProtoView => {
var hostView = this._viewFactory.getView(hostProtoView); var hostView = this._viewFactory.getView(hostProtoView);
this._viewHydrator.hydrateInPlaceHostView(null, elementOrSelector, hostView, injector); this._viewHydrator.hydrateInPlaceHostView(
parentComponentLocation, elementOrSelector, hostView, injector
);
// TODO(vsavkin): return a component ref that dehydrates the host view
// See ViewFactory.returnView
// See AppViewHydrator.dehydrateInPlaceHostView
var newLocation = hostView.elementInjectors[0].getElementRef(); var newLocation = hostView.elementInjectors[0].getElementRef();
var component = hostView.elementInjectors[0].getComponent(); var component = hostView.elementInjectors[0].getComponent();
var dispose = () => {throw "Not implemented";}; var dispose = () => {
this._viewHydrator.dehydrateInPlaceHostView(parentComponentLocation, hostView);
this._viewFactory.returnView(hostView);
};
return new ComponentRef(newLocation, component, hostView.componentChildViews[0], dispose); return new ComponentRef(newLocation, component, hostView.componentChildViews[0], dispose);
}); });
} }

View File

@ -26,27 +26,21 @@ var _staticKeys;
* @exportedAs angular2/view * @exportedAs angular2/view
*/ */
export class ElementRef { export class ElementRef {
hostView:viewModule.AppView;
boundElementIndex:number;
injector:Injector;
elementInjector:ElementInjector; elementInjector:ElementInjector;
constructor(elementInjector:ElementInjector){ constructor(elementInjector, hostView, boundElementIndex, injector){
this.elementInjector = elementInjector; this.elementInjector = elementInjector;
} this.hostView = hostView;
this.boundElementIndex = boundElementIndex;
get hostView() { this.injector = injector;
return this.elementInjector._preBuiltObjects.view;
} }
get viewContainer() { get viewContainer() {
return this.hostView.getOrCreateViewContainer(this.boundElementIndex); return this.hostView.getOrCreateViewContainer(this.boundElementIndex);
} }
get injector() {
return this.elementInjector._lightDomAppInjector;
}
get boundElementIndex() {
return this.elementInjector._proto.index;
}
} }
class StaticKeys { class StaticKeys {
@ -673,7 +667,7 @@ export class ElementInjector extends TreeNode {
} }
getElementRef() { getElementRef() {
return new ElementRef(this); return new ElementRef(this, this._preBuiltObjects.view, this._proto.index, this._lightDomAppInjector);
} }
getDynamicallyLoadedComponent() { getDynamicallyLoadedComponent() {

View File

@ -25,6 +25,9 @@ export class AppView {
elementInjectors:List<ElementInjector>; elementInjectors:List<ElementInjector>;
changeDetector:ChangeDetector; changeDetector:ChangeDetector;
componentChildViews: List<AppView>; componentChildViews: List<AppView>;
/// Host views that were added by an imperative view.
/// This is a dynamically growing / shrinking array.
imperativeHostViews: List<AppView>;
viewContainers: List<ViewContainer>; viewContainers: List<ViewContainer>;
preBuiltObjects: List<PreBuiltObjects>; preBuiltObjects: List<PreBuiltObjects>;
proto: AppProtoView; proto: AppProtoView;
@ -46,7 +49,7 @@ export class AppView {
*/ */
locals:Locals; locals:Locals;
constructor(renderer:renderApi.Renderer, viewFactory:vfModule.ViewFactory, viewHydrator:vhModule.AppViewHydrator, proto:AppProtoView, protoLocals:Map) { constructor(renderer:renderApi.Renderer, viewFactory:vfModule.ViewFactory, proto:AppProtoView, protoLocals:Map) {
this.render = null; this.render = null;
this.proto = proto; this.proto = proto;
this.changeDetector = null; this.changeDetector = null;
@ -59,7 +62,8 @@ export class AppView {
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this
this.renderer = renderer; this.renderer = renderer;
this.viewFactory = viewFactory; this.viewFactory = viewFactory;
this.viewHydrator = viewHydrator; this.viewHydrator = null;
this.imperativeHostViews = [];
} }
init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List, init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List,

View File

@ -5,7 +5,6 @@ import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {NgElement} from 'angular2/src/core/compiler/ng_element'; import {NgElement} from 'angular2/src/core/compiler/ng_element';
import * as viewModule from './view'; import * as viewModule from './view';
import {Renderer} from 'angular2/src/render/api'; import {Renderer} from 'angular2/src/render/api';
import {AppViewHydrator} from './view_hydrator';
// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this! // TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this!
export const VIEW_POOL_CAPACITY = 'ViewFactory.viewPoolCapacity'; export const VIEW_POOL_CAPACITY = 'ViewFactory.viewPoolCapacity';
@ -15,13 +14,11 @@ export class ViewFactory {
_poolCapacityPerProtoView:number; _poolCapacityPerProtoView:number;
_pooledViewsPerProtoView:Map<viewModule.AppProtoView, List<viewModule.AppView>>; _pooledViewsPerProtoView:Map<viewModule.AppProtoView, List<viewModule.AppView>>;
_renderer:Renderer; _renderer:Renderer;
_viewHydrator:AppViewHydrator;
constructor(@Inject(VIEW_POOL_CAPACITY) poolCapacityPerProtoView, renderer:Renderer, viewHydrator:AppViewHydrator) { constructor(@Inject(VIEW_POOL_CAPACITY) poolCapacityPerProtoView, renderer:Renderer) {
this._poolCapacityPerProtoView = poolCapacityPerProtoView; this._poolCapacityPerProtoView = poolCapacityPerProtoView;
this._pooledViewsPerProtoView = MapWrapper.create(); this._pooledViewsPerProtoView = MapWrapper.create();
this._renderer = renderer; this._renderer = renderer;
this._viewHydrator = viewHydrator;
} }
getView(protoView:viewModule.AppProtoView):viewModule.AppView { getView(protoView:viewModule.AppProtoView):viewModule.AppView {
@ -48,7 +45,7 @@ export class ViewFactory {
} }
_createView(protoView:viewModule.AppProtoView): viewModule.AppView { _createView(protoView:viewModule.AppProtoView): viewModule.AppView {
var view = new viewModule.AppView(this._renderer, this, this._viewHydrator, protoView, protoView.protoLocals); var view = new viewModule.AppView(this._renderer, this, protoView, protoView.protoLocals);
var changeDetector = protoView.protoChangeDetector.instantiate(view, protoView.bindings, var changeDetector = protoView.protoChangeDetector.instantiate(view, protoView.bindings,
protoView.getVariableBindings(), protoView.getdirectiveRecords()); protoView.getVariableBindings(), protoView.getdirectiveRecords());

View File

@ -7,6 +7,7 @@ import * as viewModule from './view';
import {BindingPropagationConfig, Locals} from 'angular2/change_detection'; import {BindingPropagationConfig, Locals} from 'angular2/change_detection';
import * as renderApi from 'angular2/src/render/api'; import * as renderApi from 'angular2/src/render/api';
import {ViewFactory} from 'angular2/src/core/compiler/view_factory';
/** /**
* A dehydrated view is a state of the view that allows it to be moved around * A dehydrated view is a state of the view that allows it to be moved around
@ -27,13 +28,17 @@ import * as renderApi from 'angular2/src/render/api';
@Injectable() @Injectable()
export class AppViewHydrator { export class AppViewHydrator {
_renderer:renderApi.Renderer; _renderer:renderApi.Renderer;
_viewFactory:ViewFactory;
constructor(renderer:renderApi.Renderer) { constructor(renderer:renderApi.Renderer, viewFactory:ViewFactory) {
this._renderer = renderer; this._renderer = renderer;
this._viewFactory = viewFactory;
} }
hydrateDynamicComponentView(hostView:viewModule.AppView, boundElementIndex:number, hydrateDynamicComponentView(location:eli.ElementRef,
componentView:viewModule.AppView, componentDirective:eli.DirectiveBinding, injector:Injector) { componentView:viewModule.AppView, componentDirective:eli.DirectiveBinding, injector:Injector) {
var hostView = location.hostView;
var boundElementIndex = location.boundElementIndex;
var binder = hostView.proto.elementBinders[boundElementIndex]; var binder = hostView.proto.elementBinders[boundElementIndex];
if (!binder.hasDynamicComponent()) { if (!binder.hasDynamicComponent()) {
throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`); throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`);
@ -84,16 +89,23 @@ export class AppViewHydrator {
// parentView.componentChildViews[boundElementIndex] = null; // parentView.componentChildViews[boundElementIndex] = null;
} }
hydrateInPlaceHostView(parentView:viewModule.AppView, hostElementSelector, hostView:viewModule.AppView, injector:Injector) { hydrateInPlaceHostView(parentComponentLocation:eli.ElementRef,
hostElementSelector, hostView:viewModule.AppView, injector:Injector) {
var parentRenderViewRef = null; var parentRenderViewRef = null;
if (isPresent(parentView)) { if (isPresent(parentComponentLocation)) {
// Needed for user views var parentView = parentComponentLocation.hostView.componentChildViews[parentComponentLocation.boundElementIndex];
throw new BaseException('Not yet supported'); parentRenderViewRef = parentView.render;
parentView.changeDetector.addChild(hostView.changeDetector);
ListWrapper.push(parentView.imperativeHostViews, hostView);
if (isBlank(injector)) {
injector = parentComponentLocation.injector;
} }
}
var binder = hostView.proto.elementBinders[0]; var binder = hostView.proto.elementBinders[0];
var shadowDomAppInjector = this._createShadowDomAppInjector(binder.componentDirective, injector); var shadowDomAppInjector = this._createShadowDomAppInjector(binder.componentDirective, injector);
// render views
var renderViewRefs = this._renderer.createInPlaceHostView(parentRenderViewRef, hostElementSelector, hostView.proto.render); var renderViewRefs = this._renderer.createInPlaceHostView(parentRenderViewRef, hostElementSelector, hostView.proto.render);
this._viewHydrateRecurse( this._viewHydrateRecurse(
@ -101,11 +113,13 @@ export class AppViewHydrator {
); );
} }
dehydrateInPlaceHostView(parentView:viewModule.AppView, hostView:viewModule.AppView) { dehydrateInPlaceHostView(parentComponentLocation:eli.ElementRef, hostView:viewModule.AppView) {
var parentRenderViewRef = null; var parentRenderViewRef = null;
if (isPresent(parentView)) { if (isPresent(parentComponentLocation)) {
// Needed for user views var parentView = parentComponentLocation.hostView.componentChildViews[parentComponentLocation.boundElementIndex];
throw new BaseException('Not yet supported'); parentRenderViewRef = parentView.render;
ListWrapper.remove(parentView.imperativeHostViews, hostView);
parentView.changeDetector.removeChild(hostView.changeDetector);
} }
var render = hostView.render; var render = hostView.render;
this._viewDehydrateRecurse(hostView); this._viewDehydrateRecurse(hostView);
@ -137,7 +151,7 @@ export class AppViewHydrator {
appInjector: Injector, hostElementInjector: eli.ElementInjector, appInjector: Injector, hostElementInjector: eli.ElementInjector,
context: Object, locals:Locals):number { context: Object, locals:Locals):number {
if (view.hydrated()) throw new BaseException('The view is already hydrated.'); if (view.hydrated()) throw new BaseException('The view is already hydrated.');
view.viewHydrator = this;
view.render = renderComponentViewRefs[renderComponentIndex++]; view.render = renderComponentViewRefs[renderComponentIndex++];
view.context = context; view.context = context;
@ -215,12 +229,22 @@ export class AppViewHydrator {
this._viewDehydrateRecurse(componentView); this._viewDehydrateRecurse(componentView);
var binder = view.proto.elementBinders[i]; var binder = view.proto.elementBinders[i];
if (binder.hasDynamicComponent()) { if (binder.hasDynamicComponent()) {
view.componentChildViews[i] = null;
view.changeDetector.removeShadowDomChild(componentView.changeDetector); view.changeDetector.removeShadowDomChild(componentView.changeDetector);
view.componentChildViews[i] = null;
this._viewFactory.returnView(componentView);
} }
} }
} }
// imperativeHostViews
for (var i = 0; i < view.imperativeHostViews.length; i++) {
var hostView = view.imperativeHostViews[i];
this._viewDehydrateRecurse(hostView);
view.changeDetector.removeChild(hostView.changeDetector);
this._viewFactory.returnView(hostView);
}
view.imperativeHostViews = [];
// elementInjectors // elementInjectors
for (var i = 0; i < view.elementInjectors.length; i++) { for (var i = 0; i < view.elementInjectors.length; i++) {
if (isPresent(view.elementInjectors[i])) { if (isPresent(view.elementInjectors[i])) {

View File

@ -151,7 +151,7 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter {
void appendChild(Node el, Node node) { void appendChild(Node el, Node node) {
el.append(node); el.append(node);
} }
void removeChild(Element el, Node node) { void removeChild(el, Node node) {
node.remove(); node.remove();
} }
void replaceChild(Node el, Node newNode, Node oldNode) { void replaceChild(Node el, Node newNode, Node oldNode) {

View File

@ -165,6 +165,14 @@ export class Renderer {
*/ */
createHostProtoView(componentId):Promise<ProtoViewDto> { return null; } createHostProtoView(componentId):Promise<ProtoViewDto> { return null; }
/**
* Creats a ProtoViewDto for a component that will use an imperative View using the given
* renderer.
* Note: Rigth now, the renderer argument is ignored, but will be used in the future to define
* a custom handler.
*/
createImperativeComponentProtoView(rendererId):Promise<ProtoViewDto> { return null; }
/** /**
* Compiles a single RenderProtoView. Non recursive so that * Compiles a single RenderProtoView. Non recursive so that
* we don't need to serialize all possible components over the wire, * we don't need to serialize all possible components over the wire,

View File

@ -12,6 +12,7 @@ import {Compiler} from './compiler/compiler';
import {ShadowDomStrategy} from './shadow_dom/shadow_dom_strategy'; import {ShadowDomStrategy} from './shadow_dom/shadow_dom_strategy';
import {ProtoViewBuilder} from './view/proto_view_builder'; import {ProtoViewBuilder} from './view/proto_view_builder';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {ViewContainer} from './view/view_container';
function _resolveViewContainer(vc:api.ViewContainerRef) { function _resolveViewContainer(vc:api.ViewContainerRef) {
return _resolveView(vc.view).getOrCreateViewContainer(vc.elementIndex); return _resolveView(vc.view).getOrCreateViewContainer(vc.elementIndex);
@ -90,6 +91,12 @@ export class DirectDomRenderer extends api.Renderer {
return PromiseWrapper.resolve(hostProtoViewBuilder.build()); return PromiseWrapper.resolve(hostProtoViewBuilder.build());
} }
createImperativeComponentProtoView(rendererId):Promise<api.ProtoViewDto> {
var protoViewBuilder = new ProtoViewBuilder(null);
protoViewBuilder.setImperativeRendererId(rendererId);
return PromiseWrapper.resolve(protoViewBuilder.build());
}
compile(template:api.ViewDefinition):Promise<api.ProtoViewDto> { compile(template:api.ViewDefinition):Promise<api.ProtoViewDto> {
// Note: compiler already uses a DirectDomProtoViewRef, so we don't // Note: compiler already uses a DirectDomProtoViewRef, so we don't
// need to do anything here // need to do anything here
@ -156,6 +163,21 @@ export class DirectDomRenderer extends api.Renderer {
this._viewHydrator.dehydrateInPlaceHostView(parentView, hostView); this._viewHydrator.dehydrateInPlaceHostView(parentView, hostView);
} }
setImperativeComponentRootNodes(parentViewRef:api.ViewRef, elementIndex:number, nodes:List):void {
var parentView = _resolveView(parentViewRef);
var hostElement = parentView.boundElements[elementIndex];
var componentView = parentView.componentChildViews[elementIndex];
if (isBlank(componentView)) {
throw new BaseException(`There is no componentChildView at index ${elementIndex}`);
}
if (isBlank(componentView.proto.imperativeRendererId)) {
throw new BaseException(`This component view has no imperative renderer`);
}
ViewContainer.removeViewNodes(componentView);
componentView.rootNodes = nodes;
this._shadowDomStrategy.attachTemplate(hostElement, componentView);
}
setElementProperty(viewRef:api.ViewRef, elementIndex:number, propertyName:string, propertyValue:any):void { setElementProperty(viewRef:api.ViewRef, elementIndex:number, propertyName:string, propertyValue:any):void {
_resolveView(viewRef).setElementProperty(elementIndex, propertyName, propertyValue); _resolveView(viewRef).setElementProperty(elementIndex, propertyName, propertyValue);
} }

View File

@ -11,16 +11,24 @@ export class RenderProtoView {
elementBinders:List<ElementBinder>; elementBinders:List<ElementBinder>;
isTemplateElement:boolean; isTemplateElement:boolean;
rootBindingOffset:int; rootBindingOffset:int;
imperativeRendererId:string;
constructor({ constructor({
elementBinders, elementBinders,
element element,
imperativeRendererId
}) { }) {
this.element = element; this.element = element;
this.elementBinders = elementBinders; this.elementBinders = elementBinders;
this.imperativeRendererId = imperativeRendererId;
if (isPresent(imperativeRendererId)) {
this.rootBindingOffset = 0;
this.isTemplateElement = false;
} else {
this.isTemplateElement = DOM.isTemplateElement(this.element); this.isTemplateElement = DOM.isTemplateElement(this.element);
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0; this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0;
} }
}
mergeChildComponentProtoViews(componentProtoViews:List<RenderProtoView>) { mergeChildComponentProtoViews(componentProtoViews:List<RenderProtoView>) {
var componentProtoViewIndex = 0; var componentProtoViewIndex = 0;

View File

@ -20,12 +20,18 @@ export class ProtoViewBuilder {
rootElement; rootElement;
variableBindings: Map<string, string>; variableBindings: Map<string, string>;
elements:List<ElementBinderBuilder>; elements:List<ElementBinderBuilder>;
isRootView:boolean; imperativeRendererId:string;
constructor(rootElement) { constructor(rootElement) {
this.rootElement = rootElement; this.rootElement = rootElement;
this.elements = []; this.elements = [];
this.variableBindings = MapWrapper.create(); this.variableBindings = MapWrapper.create();
this.imperativeRendererId = null;
}
setImperativeRendererId(id:string):ProtoViewBuilder {
this.imperativeRendererId = id;
return this;
} }
bindElement(element, description = null):ElementBinderBuilder { bindElement(element, description = null):ElementBinderBuilder {
@ -90,7 +96,8 @@ export class ProtoViewBuilder {
return new api.ProtoViewDto({ return new api.ProtoViewDto({
render: new directDomRenderer.DirectDomProtoViewRef(new RenderProtoView({ render: new directDomRenderer.DirectDomProtoViewRef(new RenderProtoView({
element: this.rootElement, element: this.rootElement,
elementBinders: renderElementBinders elementBinders: renderElementBinders,
imperativeRendererId: this.imperativeRendererId
})), })),
elementBinders: apiElementBinders, elementBinders: apiElementBinders,
variableBindings: this.variableBindings variableBindings: this.variableBindings

View File

@ -31,6 +31,9 @@ export class RenderView {
hydrated: boolean; hydrated: boolean;
_eventDispatcher: any/*EventDispatcher*/; _eventDispatcher: any/*EventDispatcher*/;
eventHandlerRemovers: List<Function>; eventHandlerRemovers: List<Function>;
/// Host views that were added by an imperative view.
/// This is a dynamically growing / shrinking array.
imperativeHostViews: List<RenderView>;
constructor( constructor(
proto:RenderProtoView, rootNodes:List, proto:RenderProtoView, rootNodes:List,
@ -46,7 +49,8 @@ export class RenderView {
this.componentChildViews = ListWrapper.createFixedSize(boundElements.length); this.componentChildViews = ListWrapper.createFixedSize(boundElements.length);
this.hostLightDom = null; this.hostLightDom = null;
this.hydrated = false; this.hydrated = false;
this.eventHandlerRemovers = null; this.eventHandlerRemovers = [];
this.imperativeHostViews = [];
} }
getDirectParentLightDom(boundElementIndex:number) { getDirectParentLightDom(boundElementIndex:number) {

View File

@ -58,6 +58,12 @@ export class ViewFactory {
} }
_createView(protoView:pvModule.RenderProtoView, inplaceElement): viewModule.RenderView { _createView(protoView:pvModule.RenderProtoView, inplaceElement): viewModule.RenderView {
if (isPresent(protoView.imperativeRendererId)) {
return new viewModule.RenderView(
protoView, [], [], [], []
);
}
var rootElementClone = isPresent(inplaceElement) ? inplaceElement : DOM.importIntoDoc(protoView.element); var rootElementClone = isPresent(inplaceElement) ? inplaceElement : DOM.importIntoDoc(protoView.element);
var elementsWithBindingsDynamic; var elementsWithBindingsDynamic;
if (protoView.isTemplateElement) { if (protoView.isTemplateElement) {
@ -125,7 +131,7 @@ export class ViewFactory {
// static child components // static child components
if (binder.hasStaticComponent()) { if (binder.hasStaticComponent()) {
var childView = this._createView(binder.nestedProtoView, null); var childView = this._createView(binder.nestedProtoView, null);
this.setComponentView(view, binderIdx, childView); ViewFactory.setComponentView(this._shadowDomStrategy, view, binderIdx, childView);
} }
// events // events
@ -148,10 +154,10 @@ export class ViewFactory {
// This method is used by the ViewFactory and the ViewHydrator // This method is used by the ViewFactory and the ViewHydrator
// TODO(tbosch): change shadow dom emulation so that LightDom // TODO(tbosch): change shadow dom emulation so that LightDom
// instances don't need to be recreated by instead hydrated/dehydrated // instances don't need to be recreated by instead hydrated/dehydrated
setComponentView(hostView:viewModule.RenderView, elementIndex:number, componentView:viewModule.RenderView) { static setComponentView(shadowDomStrategy:ShadowDomStrategy, hostView:viewModule.RenderView, elementIndex:number, componentView:viewModule.RenderView) {
var element = hostView.boundElements[elementIndex]; var element = hostView.boundElements[elementIndex];
var lightDom = this._shadowDomStrategy.constructLightDom(hostView, componentView, element); var lightDom = shadowDomStrategy.constructLightDom(hostView, componentView, element);
this._shadowDomStrategy.attachTemplate(element, componentView); shadowDomStrategy.attachTemplate(element, componentView);
hostView.lightDoms[elementIndex] = lightDom; hostView.lightDoms[elementIndex] = lightDom;
hostView.componentChildViews[elementIndex] = componentView; hostView.componentChildViews[elementIndex] = componentView;
} }

View File

@ -7,6 +7,7 @@ import {EventManager} from '../events/event_manager';
import {ViewFactory} from './view_factory'; import {ViewFactory} from './view_factory';
import * as vcModule from './view_container'; import * as vcModule from './view_container';
import * as viewModule from './view'; import * as viewModule from './view';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
/** /**
* A dehydrated view is a state of the view that allows it to be moved around * A dehydrated view is a state of the view that allows it to be moved around
@ -23,14 +24,16 @@ import * as viewModule from './view';
export class RenderViewHydrator { export class RenderViewHydrator {
_eventManager:EventManager; _eventManager:EventManager;
_viewFactory:ViewFactory; _viewFactory:ViewFactory;
_shadowDomStrategy:ShadowDomStrategy;
constructor(eventManager:EventManager, viewFactory:ViewFactory) { constructor(eventManager:EventManager, viewFactory:ViewFactory, shadowDomStrategy:ShadowDomStrategy) {
this._eventManager = eventManager; this._eventManager = eventManager;
this._viewFactory = viewFactory; this._viewFactory = viewFactory;
this._shadowDomStrategy = shadowDomStrategy;
} }
hydrateDynamicComponentView(hostView:viewModule.RenderView, boundElementIndex:number, componentView:viewModule.RenderView) { hydrateDynamicComponentView(hostView:viewModule.RenderView, boundElementIndex:number, componentView:viewModule.RenderView) {
this._viewFactory.setComponentView(hostView, boundElementIndex, componentView); ViewFactory.setComponentView(this._shadowDomStrategy, hostView, boundElementIndex, componentView);
var lightDom = hostView.lightDoms[boundElementIndex]; var lightDom = hostView.lightDoms[boundElementIndex];
this._viewHydrateRecurse(componentView, lightDom); this._viewHydrateRecurse(componentView, lightDom);
if (isPresent(lightDom)) { if (isPresent(lightDom)) {
@ -50,15 +53,17 @@ export class RenderViewHydrator {
hydrateInPlaceHostView(parentView:viewModule.RenderView, hostView:viewModule.RenderView) { hydrateInPlaceHostView(parentView:viewModule.RenderView, hostView:viewModule.RenderView) {
if (isPresent(parentView)) { if (isPresent(parentView)) {
throw new BaseException('Not supported yet'); ListWrapper.push(parentView.imperativeHostViews, hostView);
} }
this._viewHydrateRecurse(hostView, null); this._viewHydrateRecurse(hostView, null);
} }
dehydrateInPlaceHostView(parentView:viewModule.RenderView, hostView:viewModule.RenderView) { dehydrateInPlaceHostView(parentView:viewModule.RenderView, hostView:viewModule.RenderView) {
if (isPresent(parentView)) { if (isPresent(parentView)) {
throw new BaseException('Not supported yet'); ListWrapper.remove(parentView.imperativeHostViews, hostView);
} }
vcModule.ViewContainer.removeViewNodes(hostView);
hostView.rootNodes = [];
this._viewDehydrateRecurse(hostView); this._viewDehydrateRecurse(hostView);
} }
@ -130,12 +135,24 @@ export class RenderViewHydrator {
this._viewDehydrateRecurse(cv); this._viewDehydrateRecurse(cv);
if (view.proto.elementBinders[i].hasDynamicComponent()) { if (view.proto.elementBinders[i].hasDynamicComponent()) {
vcModule.ViewContainer.removeViewNodes(cv); vcModule.ViewContainer.removeViewNodes(cv);
this._viewFactory.returnView(cv);
view.lightDoms[i] = null; view.lightDoms[i] = null;
view.componentChildViews[i] = null; view.componentChildViews[i] = null;
} }
} }
} }
// imperativeHostViews
for (var i = 0; i < view.imperativeHostViews.length; i++) {
var hostView = view.imperativeHostViews[i];
this._viewDehydrateRecurse(hostView);
vcModule.ViewContainer.removeViewNodes(hostView);
hostView.rootNodes = [];
this._viewFactory.returnView(hostView);
}
view.imperativeHostViews = [];
// viewContainers and content tags // viewContainers and content tags
if (isPresent(view.viewContainers)) { if (isPresent(view.viewContainers)) {
for (var i = 0; i < view.viewContainers.length; i++) { for (var i = 0; i < view.viewContainers.length; i++) {

View File

@ -79,6 +79,7 @@ function _getAppBindings() {
bind(ShadowDomStrategy).toFactory( bind(ShadowDomStrategy).toFactory(
(styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head), (styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head),
[StyleUrlResolver, appDocumentToken]), [StyleUrlResolver, appDocumentToken]),
bind(DirectDomRenderer).toClass(DirectDomRenderer),
bind(Renderer).toClass(DirectDomRenderer), bind(Renderer).toClass(DirectDomRenderer),
bind(rc.Compiler).toClass(rc.DefaultCompiler), bind(rc.Compiler).toClass(rc.DefaultCompiler),
rvf.ViewFactory, rvf.ViewFactory,

View File

@ -10,10 +10,11 @@ import {
inject, inject,
IS_DARTIUM, IS_DARTIUM,
it, it,
SpyObject, proxy
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {List, ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {Type, isBlank, stringify, isPresent} from 'angular2/src/facade/lang'; import {IMPLEMENTS, Type, isBlank, stringify, isPresent} from 'angular2/src/facade/lang';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler'; import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
@ -30,20 +31,28 @@ import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
import {UrlResolver} from 'angular2/src/services/url_resolver'; import {UrlResolver} from 'angular2/src/services/url_resolver';
import * as renderApi from 'angular2/src/render/api'; import * as renderApi from 'angular2/src/render/api';
// TODO(tbosch): Spys don't support named modules...
import {Renderer} from 'angular2/src/render/api';
export function main() { export function main() {
describe('compiler', function() { describe('compiler', function() {
var reader, tplResolver, renderer, protoViewFactory, cmpUrlMapper; var reader, tplResolver, renderer, protoViewFactory, cmpUrlMapper, renderCompileRequests;
beforeEach(() => { beforeEach(() => {
reader = new DirectiveMetadataReader(); reader = new DirectiveMetadataReader();
tplResolver = new FakeTemplateResolver(); tplResolver = new FakeTemplateResolver();
cmpUrlMapper = new RuntimeComponentUrlMapper(); cmpUrlMapper = new RuntimeComponentUrlMapper();
renderer = new SpyRenderer();
}); });
function createCompiler(renderCompileResults:List, protoViewFactoryResults:List<AppProtoView>) { function createCompiler(renderCompileResults:List, protoViewFactoryResults:List<AppProtoView>) {
var urlResolver = new FakeUrlResolver(); var urlResolver = new FakeUrlResolver();
renderer = new FakeRenderer(renderCompileResults); renderCompileRequests = [];
renderer.spy('compile').andCallFake( (template) => {
ListWrapper.push(renderCompileRequests, template);
return PromiseWrapper.resolve(ListWrapper.removeAt(renderCompileResults, 0));
});
protoViewFactory = new FakeProtoViewFactory(protoViewFactoryResults) protoViewFactory = new FakeProtoViewFactory(protoViewFactoryResults)
return new Compiler( return new Compiler(
reader, reader,
@ -62,8 +71,8 @@ export function main() {
tplResolver.setView(MainComponent, template); tplResolver.setView(MainComponent, template);
var compiler = createCompiler([createRenderProtoView()], [createProtoView()]); var compiler = createCompiler([createRenderProtoView()], [createProtoView()]);
return compiler.compile(MainComponent).then( (protoView) => { return compiler.compile(MainComponent).then( (protoView) => {
expect(renderer.requests.length).toBe(1); expect(renderCompileRequests.length).toBe(1);
return renderer.requests[0]; return renderCompileRequests[0];
}); });
} }
@ -362,6 +371,11 @@ export function main() {
})); }));
it('should create host proto views', inject([AsyncTestCompleter], (async) => { it('should create host proto views', inject([AsyncTestCompleter], (async) => {
renderer.spy('createHostProtoView').andCallFake( (componentId) => {
return PromiseWrapper.resolve(
createRenderProtoView([createRenderComponentElementBinder(0)])
);
});
tplResolver.setView(MainComponent, new View({template: '<div></div>'})); tplResolver.setView(MainComponent, new View({template: '<div></div>'}));
var rootProtoView = createProtoView([ var rootProtoView = createProtoView([
createComponentElementBinder(reader, MainComponent) createComponentElementBinder(reader, MainComponent)
@ -379,6 +393,25 @@ export function main() {
async.done(); async.done();
}); });
})); }));
it('should create imperative proto views', inject([AsyncTestCompleter], (async) => {
renderer.spy('createImperativeComponentProtoView').andCallFake( (rendererId) => {
return PromiseWrapper.resolve(
createRenderProtoView([])
);
});
tplResolver.setView(MainComponent, new View({renderer: 'some-renderer'}));
var mainProtoView = createProtoView();
var compiler = createCompiler(
[],
[mainProtoView]
);
compiler.compile(MainComponent).then( (protoView) => {
expect(protoView).toBe(mainProtoView);
expect(renderer.spy('createImperativeComponentProtoView')).toHaveBeenCalledWith('some-renderer');
async.done();
});
}));
}); });
} }
@ -482,26 +515,11 @@ class DirectiveWithAttributes {
constructor(@Attribute('someAttr') someAttr:string) {} constructor(@Attribute('someAttr') someAttr:string) {}
} }
class FakeRenderer extends renderApi.Renderer { @proxy
requests:List<renderApi.ViewDefinition>; @IMPLEMENTS(Renderer)
_results:List; class SpyRenderer extends SpyObject {
constructor(){super(Renderer);}
constructor(results) { noSuchMethod(m){return super.noSuchMethod(m)}
super();
this._results = results;
this.requests = [];
}
compile(template:renderApi.ViewDefinition):Promise<renderApi.ProtoViewDto> {
ListWrapper.push(this.requests, template);
return PromiseWrapper.resolve(ListWrapper.removeAt(this._results, 0));
}
createHostProtoView(componentId):Promise<renderApi.ProtoViewDto> {
return PromiseWrapper.resolve(
createRenderProtoView([createRenderComponentElementBinder(0)])
);
}
} }
class FakeUrlResolver extends UrlResolver { class FakeUrlResolver extends UrlResolver {

View File

@ -21,6 +21,7 @@ import {View} from 'angular2/src/core/annotations/view';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
import {ElementRef} from 'angular2/src/core/compiler/element_injector'; import {ElementRef} from 'angular2/src/core/compiler/element_injector';
import {If} from 'angular2/src/directives/if'; import {If} from 'angular2/src/directives/if';
import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer';
export function main() { export function main() {
describe('DynamicComponentLoader', function () { describe('DynamicComponentLoader', function () {
@ -134,7 +135,69 @@ export function main() {
}); });
})); }));
}); });
describe('loading into a new location', () => {
it('should allow to create, update and destroy components',
inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideView(MyComp, new View({
template: '<imp-ng-cmp #impview></imp-ng-cmp>',
directives: [ImperativeViewComponentUsingNgComponent]
}));
tb.createView(MyComp).then((view) => {
var userViewComponent = view.rawView.locals.get("impview");
userViewComponent.done.then((childComponentRef) => {
view.detectChanges();
expect(view.rootNodes).toHaveText('hello');
childComponentRef.instance.ctxProp = 'new';
view.detectChanges();
expect(view.rootNodes).toHaveText('new');
childComponentRef.dispose();
expect(view.rootNodes).toHaveText('');
async.done();
}); });
});
}));
});
});
}
@Component({
selector: 'imp-ng-cmp'
})
@View({
renderer: 'imp-ng-cmp-renderer'
})
class ImperativeViewComponentUsingNgComponent {
done;
constructor(self:ElementRef, dynamicComponentLoader:DynamicComponentLoader, renderer:DirectDomRenderer) {
var div = el('<div></div>');
renderer.setImperativeComponentRootNodes(self.hostView.render, self.boundElementIndex, [div]);
this.done = dynamicComponentLoader.loadIntoNewLocation(ChildComp, self, div, null);
}
}
@Component({
selector: 'child-cmp',
})
@View({
template: '{{ctxProp}}'
})
class ChildComp {
ctxProp:string;
constructor() {
this.ctxProp = 'hello';
}
} }

View File

@ -714,7 +714,7 @@ export function main() {
beforeEach( () => { beforeEach( () => {
renderer = new FakeRenderer(); renderer = new FakeRenderer();
var protoView = new AppProtoView(null, null); var protoView = new AppProtoView(null, null);
view = new AppView(renderer, null, null, protoView, MapWrapper.create()); view = new AppView(renderer, null, protoView, MapWrapper.create());
view.render = new ViewRef(); view.render = new ViewRef();
}); });

View File

@ -33,6 +33,9 @@ import {If} from 'angular2/src/directives/if';
import {ViewContainer} from 'angular2/src/core/compiler/view_container'; import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {Compiler} from 'angular2/src/core/compiler/compiler'; import {Compiler} from 'angular2/src/core/compiler/compiler';
import {ElementRef} from 'angular2/src/core/compiler/element_injector';
import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer';
export function main() { export function main() {
describe('integration tests', function() { describe('integration tests', function() {
@ -722,6 +725,18 @@ export function main() {
})); }));
}); });
it('should support imperative views',
inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideView(MyComp, new View({
template: '<simple-imp-cmp></simple-imp-cmp>',
directives: [SimpleImperativeViewComponent]
}));
tb.createView(MyComp).then((view) => {
expect(view.rootNodes).toHaveText('hello imp view');
async.done();
});
}));
// Disabled until a solution is found, refs: // Disabled until a solution is found, refs:
// - https://github.com/angular/angular/issues/776 // - https://github.com/angular/angular/issues/776
// - https://github.com/angular/angular/commit/81f3f32 // - https://github.com/angular/angular/commit/81f3f32
@ -783,6 +798,21 @@ export function main() {
}); });
} }
@Component({
selector: 'simple-imp-cmp'
})
@View({
renderer: 'simple-imp-cmp-renderer'
})
class SimpleImperativeViewComponent {
done;
constructor(self:ElementRef, renderer:DirectDomRenderer) {
renderer.setImperativeComponentRootNodes(self.hostView.render, self.boundElementIndex, [el('hello imp view')]);
}
}
@Decorator({ @Decorator({
selector: 'dynamic-vp' selector: 'dynamic-vp'
}) })
@ -888,7 +918,7 @@ class ComponentWithPipes {
@Component({ @Component({
selector: 'child-cmp', selector: 'child-cmp',
injectables: [MyService] injectables: [MyService],
}) })
@View({ @View({
directives: [MyDir], directives: [MyDir],

View File

@ -35,7 +35,7 @@ export function main() {
}); });
function createViewFactory({capacity}):ViewFactory { function createViewFactory({capacity}):ViewFactory {
return new ViewFactory(capacity, renderer, null); return new ViewFactory(capacity, renderer);
} }
function createProtoChangeDetector() { function createProtoChangeDetector() {

View File

@ -21,21 +21,24 @@ import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
import {Renderer, ViewRef} from 'angular2/src/render/api'; import {Renderer, ViewRef} from 'angular2/src/render/api';
import {ChangeDetector} from 'angular2/change_detection'; import {ChangeDetector} from 'angular2/change_detection';
import {ElementBinder} from 'angular2/src/core/compiler/element_binder'; import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {DirectiveBinding, ElementInjector} from 'angular2/src/core/compiler/element_injector'; import {DirectiveBinding, ElementInjector, ElementRef} from 'angular2/src/core/compiler/element_injector';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {Component} from 'angular2/src/core/annotations/annotations'; import {Component} from 'angular2/src/core/annotations/annotations';
import {AppViewHydrator} from 'angular2/src/core/compiler/view_hydrator'; import {AppViewHydrator} from 'angular2/src/core/compiler/view_hydrator';
import {ViewFactory} from 'angular2/src/core/compiler/view_factory';
export function main() { export function main() {
describe('AppViewHydrator', () => { describe('AppViewHydrator', () => {
var renderer; var renderer;
var reader; var reader;
var hydrator; var hydrator;
var viewFactory;
beforeEach( () => { beforeEach( () => {
renderer = new SpyRenderer(); renderer = new SpyRenderer();
reader = new DirectiveMetadataReader(); reader = new DirectiveMetadataReader();
hydrator = new AppViewHydrator(renderer); viewFactory = new SpyViewFactory();
hydrator = new AppViewHydrator(renderer, viewFactory);
}); });
function createDirectiveBinding(type) { function createDirectiveBinding(type) {
@ -81,14 +84,14 @@ export function main() {
} }
function createEmptyView() { function createEmptyView() {
var view = new AppView(renderer, null, null, createProtoView(), MapWrapper.create()); var view = new AppView(renderer, null, createProtoView(), MapWrapper.create());
var changeDetector = new SpyChangeDetector(); var changeDetector = new SpyChangeDetector();
view.init(changeDetector, [], [], [], []); view.init(changeDetector, [], [], [], []);
return view; return view;
} }
function createHostView(pv, shadowView, componentInstance, elementInjectors = null) { function createHostView(pv, shadowView, componentInstance, elementInjectors = null) {
var view = new AppView(renderer, null, null, pv, MapWrapper.create()); var view = new AppView(renderer, null, pv, MapWrapper.create());
var changeDetector = new SpyChangeDetector(); var changeDetector = new SpyChangeDetector();
var eis; var eis;
@ -117,7 +120,7 @@ export function main() {
var view = createHostView(pv, null, null); var view = createHostView(pv, null, null);
var shadowView = createEmptyView(); var shadowView = createEmptyView();
expect( expect(
() => hydrator.hydrateDynamicComponentView(view, 0, shadowView, null, null) () => hydrator.hydrateDynamicComponentView(new ElementRef(null, view, 0, null), shadowView, null, null)
).toThrowError('There is no dynamic component directive at element 0'); ).toThrowError('There is no dynamic component directive at element 0');
}); });
@ -126,7 +129,7 @@ export function main() {
var view = createHostView(pv, null, null); var view = createHostView(pv, null, null);
var shadowView = createEmptyView(); var shadowView = createEmptyView();
expect( expect(
() => hydrator.hydrateDynamicComponentView(view, 0, shadowView, null, null) () => hydrator.hydrateDynamicComponentView(new ElementRef(null, view, 0, null), shadowView, null, null)
).toThrowError('There is no dynamic component directive at element 0'); ).toThrowError('There is no dynamic component directive at element 0');
}); });
@ -135,9 +138,10 @@ export function main() {
var shadowView = createEmptyView(); var shadowView = createEmptyView();
var view = createHostView(pv, null, null); var view = createHostView(pv, null, null);
renderer.spy('createDynamicComponentView').andReturn([new ViewRef(), new ViewRef()]); renderer.spy('createDynamicComponentView').andReturn([new ViewRef(), new ViewRef()]);
hydrator.hydrateDynamicComponentView(view, 0, shadowView, createDirectiveBinding(SomeComponent), null); var elRef = new ElementRef(null, view, 0, null);
hydrator.hydrateDynamicComponentView(elRef, shadowView, createDirectiveBinding(SomeComponent), null);
expect( expect(
() => hydrator.hydrateDynamicComponentView(view, 0, shadowView, null, null) () => hydrator.hydrateDynamicComponentView(elRef, shadowView, null, null)
).toThrowError('There already is a bound component at element 0'); ).toThrowError('There already is a bound component at element 0');
}); });
@ -217,6 +221,7 @@ export function main() {
expect(hostView.componentChildViews[0]).toBe(shadowView); expect(hostView.componentChildViews[0]).toBe(shadowView);
expect(hostView.changeDetector.spy('removeShadowDomChild')).not.toHaveBeenCalled(); expect(hostView.changeDetector.spy('removeShadowDomChild')).not.toHaveBeenCalled();
expect(viewFactory.spy('returnView')).not.toHaveBeenCalled();
}); });
it('should clear dynamic child components', () => { it('should clear dynamic child components', () => {
@ -225,6 +230,19 @@ export function main() {
expect(hostView.componentChildViews[0]).toBe(null); expect(hostView.componentChildViews[0]).toBe(null);
expect(hostView.changeDetector.spy('removeShadowDomChild')).toHaveBeenCalledWith(shadowView.changeDetector); expect(hostView.changeDetector.spy('removeShadowDomChild')).toHaveBeenCalledWith(shadowView.changeDetector);
expect(viewFactory.spy('returnView')).toHaveBeenCalledWith(shadowView);
});
it('should clear imperatively added child components', () => {
createAndHydrate(createProtoView());
var impHostView = createHostView(createHostProtoView(createProtoView()), createEmptyView(), null);
shadowView.imperativeHostViews = [impHostView];
dehydrate(hostView);
expect(shadowView.imperativeHostViews).toEqual([]);
expect(viewFactory.spy('returnView')).toHaveBeenCalledWith(impHostView);
expect(shadowView.changeDetector.spy('removeChild')).toHaveBeenCalledWith(impHostView.changeDetector);
}); });
}); });
@ -255,3 +273,10 @@ class SpyElementInjector extends SpyObject {
constructor(){super(ElementInjector);} constructor(){super(ElementInjector);}
noSuchMethod(m){return super.noSuchMethod(m)} noSuchMethod(m){return super.noSuchMethod(m)}
} }
@proxy
@IMPLEMENTS(ViewFactory)
class SpyViewFactory extends SpyObject {
constructor(){super(ViewFactory);}
noSuchMethod(m){return super.noSuchMethod(m)}
}

View File

@ -57,7 +57,7 @@ export function main() {
} }
function createViewWithOneBoundElement(pv) { function createViewWithOneBoundElement(pv) {
var view = new AppView(renderer, null, null, pv, MapWrapper.create()); var view = new AppView(renderer, null, pv, MapWrapper.create());
var changeDetector = new SpyChangeDetector(); var changeDetector = new SpyChangeDetector();
var eij = createElementInjector(); var eij = createElementInjector();
view.init(changeDetector, [eij], [eij], view.init(changeDetector, [eij], [eij],

View File

@ -50,6 +50,16 @@ export function main() {
}); });
})); }));
it('should create imperative proto views', inject([AsyncTestCompleter], (async) => {
createRenderer();
renderer.createImperativeComponentProtoView('someRenderId').then( (rootProtoView) => {
expect(rootProtoView.elementBinders).toEqual([]);
expect(rootProtoView.render.delegate.imperativeRendererId).toBe('someRenderId');
async.done();
});
}));
it('should add a static component', inject([AsyncTestCompleter], (async) => { it('should add a static component', inject([AsyncTestCompleter], (async) => {
createRenderer(); createRenderer();
renderer.createHostProtoView('someComponentId').then( (rootProtoView) => { renderer.createHostProtoView('someComponentId').then( (rootProtoView) => {

View File

@ -46,7 +46,7 @@ export class IntegrationTestbed {
this.eventPlugin = new FakeEventManagerPlugin(); this.eventPlugin = new FakeEventManagerPlugin();
var eventManager = new EventManager([this.eventPlugin], new FakeVmTurnZone()); var eventManager = new EventManager([this.eventPlugin], new FakeVmTurnZone());
var viewFactory = new ViewFactory(viewCacheCapacity, eventManager, shadowDomStrategy); var viewFactory = new ViewFactory(viewCacheCapacity, eventManager, shadowDomStrategy);
var viewHydrator = new RenderViewHydrator(eventManager, viewFactory); var viewHydrator = new RenderViewHydrator(eventManager, viewFactory, shadowDomStrategy);
this.renderer = new DirectDomRenderer(compiler, viewFactory, viewHydrator, shadowDomStrategy); this.renderer = new DirectDomRenderer(compiler, viewFactory, viewHydrator, shadowDomStrategy);
} }

View File

@ -14,7 +14,7 @@ import {
xit, xit,
SpyObject, proxy SpyObject, proxy
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang'; import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang';
import {RenderProtoView} from 'angular2/src/render/dom/view/proto_view'; import {RenderProtoView} from 'angular2/src/render/dom/view/proto_view';
import {ElementBinder} from 'angular2/src/render/dom/view/element_binder'; import {ElementBinder} from 'angular2/src/render/dom/view/element_binder';
@ -76,7 +76,7 @@ export function main() {
function createHostView(pv, shadowDomView) { function createHostView(pv, shadowDomView) {
var view = new RenderView(pv, [el('<div></div>')], var view = new RenderView(pv, [el('<div></div>')],
[], [el('<div></div>')], [null]); [], [el('<div></div>')], [null]);
viewFactory.setComponentView(view, 0, shadowDomView); ViewFactory.setComponentView(shadowDomStrategy, view, 0, shadowDomView);
return view; return view;
} }
@ -94,8 +94,8 @@ export function main() {
shadowDomStrategy.spy('constructLightDom').andCallFake( (lightDomView, shadowDomView, el) => { shadowDomStrategy.spy('constructLightDom').andCallFake( (lightDomView, shadowDomView, el) => {
return new SpyLightDom(); return new SpyLightDom();
}); });
viewFactory = new ViewFactory(1, eventManager, shadowDomStrategy); viewFactory = new SpyViewFactory();
viewHydrator = new RenderViewHydrator(eventManager, viewFactory); viewHydrator = new RenderViewHydrator(eventManager, viewFactory, shadowDomStrategy);
}); });
describe('hydrateDynamicComponentView', () => { describe('hydrateDynamicComponentView', () => {
@ -111,6 +111,59 @@ export function main() {
}); });
describe('hydrateInPlaceHostView', () => {
function createInPlaceHostView() {
var hostPv = createHostProtoView(createProtoView());
var shadowView = createEmptyView();
return createHostView(hostPv, shadowView);
}
it('should hydrate the view', () => {
var hostView = createInPlaceHostView();
viewHydrator.hydrateInPlaceHostView(null, hostView);
expect(hostView.hydrated).toBe(true);
});
it('should store the view in the parent view', () => {
var parentView = createEmptyView();
var hostView = createInPlaceHostView();
viewHydrator.hydrateInPlaceHostView(parentView, hostView);
expect(parentView.imperativeHostViews).toEqual([hostView]);
});
});
describe('dehydrateInPlaceHostView', () => {
function createAndHydrateInPlaceHostView(parentView) {
var hostPv = createHostProtoView(createProtoView());
var shadowView = createEmptyView();
var hostView = createHostView(hostPv, shadowView);
viewHydrator.hydrateInPlaceHostView(parentView, hostView);
return hostView;
}
it('should clear the host view', () => {
var parentView = createEmptyView();
var hostView = createAndHydrateInPlaceHostView(parentView);
var rootNodes = hostView.rootNodes;
expect(rootNodes[0].parentNode).toBeTruthy();
viewHydrator.dehydrateInPlaceHostView(parentView, hostView);
expect(parentView.imperativeHostViews).toEqual([]);
expect(rootNodes[0].parentNode).toBeFalsy();
expect(hostView.rootNodes).toEqual([]);
});
});
describe('hydrate... shared functionality', () => { describe('hydrate... shared functionality', () => {
it('should hydrate existing child components', () => { it('should hydrate existing child components', () => {
@ -128,9 +181,12 @@ export function main() {
describe('dehydrate... shared functionality', () => { describe('dehydrate... shared functionality', () => {
var hostView; var hostView;
function createAndHydrate(nestedProtoView, shadowView) { function createAndHydrate(nestedProtoView, shadowView, imperativeHostView = null) {
var hostPv = createHostProtoView(nestedProtoView); var hostPv = createHostProtoView(nestedProtoView);
hostView = createHostView(hostPv, shadowView); hostView = createHostView(hostPv, shadowView);
if (isPresent(imperativeHostView)) {
viewHydrator.hydrateInPlaceHostView(hostView, imperativeHostView);
}
hydrate(hostView); hydrate(hostView);
} }
@ -152,15 +208,36 @@ export function main() {
expect(hostView.componentChildViews[0]).toBe(shadowView); expect(hostView.componentChildViews[0]).toBe(shadowView);
expect(shadowView.rootNodes[0].parentNode).toBeTruthy(); expect(shadowView.rootNodes[0].parentNode).toBeTruthy();
expect(viewFactory.spy('returnView')).not.toHaveBeenCalled();
}); });
it('should clear dynamic child components', () => { it('should clear dynamic child components', () => {
var shadowView = createEmptyView(); var shadowView = createEmptyView();
createAndHydrate(null, shadowView); createAndHydrate(null, shadowView);
expect(shadowView.rootNodes[0].parentNode).toBeTruthy();
dehydrate(hostView); dehydrate(hostView);
expect(hostView.componentChildViews[0]).toBe(null); expect(hostView.componentChildViews[0]).toBe(null);
expect(shadowView.rootNodes[0].parentNode).toBe(null); expect(shadowView.rootNodes[0].parentNode).toBe(null);
expect(viewFactory.spy('returnView')).toHaveBeenCalledWith(shadowView);
});
it('should clear imperatively added child components', () => {
var shadowView = createEmptyView();
createAndHydrate(createProtoView(), shadowView);
var impHostView = createHostView(createHostProtoView(createProtoView()), createEmptyView());
shadowView.imperativeHostViews = [impHostView];
var rootNodes = impHostView.rootNodes;
expect(rootNodes[0].parentNode).toBeTruthy();
dehydrate(hostView);
expect(shadowView.imperativeHostViews).toEqual([]);
expect(impHostView.rootNodes).toEqual([]);
expect(rootNodes[0].parentNode).toBeFalsy();
expect(viewFactory.spy('returnView')).toHaveBeenCalledWith(impHostView);
}); });
}); });
@ -189,3 +266,9 @@ class SpyLightDom extends SpyObject {
noSuchMethod(m){return super.noSuchMethod(m)} noSuchMethod(m){return super.noSuchMethod(m)}
} }
@proxy
@IMPLEMENTS(ViewFactory)
class SpyViewFactory extends SpyObject {
constructor(){super(ViewFactory);}
noSuchMethod(m){return super.noSuchMethod(m)}
}