feat(view): add imperative views
This commit is contained in:
		
							parent
							
								
									817c79ca77
								
							
						
					
					
						commit
						ada1e642c5
					
				
							
								
								
									
										2
									
								
								modules/angular2/angular2.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								modules/angular2/angular2.js
									
									
									
									
										vendored
									
									
								
							| @ -4,3 +4,5 @@ export * from './annotations'; | ||||
| export * from './directives'; | ||||
| export * from './forms'; | ||||
| export {Observable, EventEmitter} from 'angular2/src/facade/async'; | ||||
| export * from 'angular2/src/render/api'; | ||||
| export {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer'; | ||||
|  | ||||
							
								
								
									
										13
									
								
								modules/angular2/src/core/annotations/view.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								modules/angular2/src/core/annotations/view.js
									
									
									
									
										vendored
									
									
								
							| @ -71,19 +71,28 @@ export class View { | ||||
|    */ | ||||
|   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() | ||||
|   constructor({ | ||||
|       templateUrl, | ||||
|       template, | ||||
|       directives | ||||
|       directives, | ||||
|       renderer | ||||
|     }: { | ||||
|       templateUrl: string, | ||||
|       template: string, | ||||
|       directives: List<Type> | ||||
|       directives: List<Type>, | ||||
|       renderer: string | ||||
|     }) | ||||
|   { | ||||
|     this.templateUrl = templateUrl; | ||||
|     this.template = template; | ||||
|     this.directives = directives; | ||||
|     this.renderer = renderer; | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										7
									
								
								modules/angular2/src/core/application.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								modules/angular2/src/core/application.js
									
									
									
									
										vendored
									
									
								
							| @ -75,7 +75,7 @@ function _injectorBindings(appComponentType): List<Binding> { | ||||
|         // We need to do this here to ensure that we create Testability and
 | ||||
|         // it's ready on the window for users.
 | ||||
|         registry.registerApplication(appElement, testability); | ||||
|         return dynamicComponentLoader.loadIntoNewLocation(appElement, appComponentAnnotatedType.type, injector); | ||||
|         return dynamicComponentLoader.loadIntoNewLocation(appComponentAnnotatedType.type, null, appElement, injector); | ||||
|       }, [DynamicComponentLoader, Injector, appElementToken, appComponentAnnotatedTypeToken, | ||||
|         Testability, TestabilityRegistry]), | ||||
| 
 | ||||
| @ -91,6 +91,7 @@ function _injectorBindings(appComponentType): List<Binding> { | ||||
|       bind(ShadowDomStrategy).toFactory( | ||||
|           (styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head), | ||||
|           [StyleUrlResolver, appDocumentToken]), | ||||
|       DirectDomRenderer, | ||||
|       bind(Renderer).toClass(DirectDomRenderer), | ||||
|       bind(rc.Compiler).toClass(rc.DefaultCompiler), | ||||
|       // 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
 | ||||
|       // we are getting errors in dart2js with mirrors...
 | ||||
|       bind(ViewFactory).toFactory( | ||||
|         (capacity, renderer, appViewHydrator) => new ViewFactory(capacity, renderer, appViewHydrator), | ||||
|         [VIEW_POOL_CAPACITY, Renderer, AppViewHydrator] | ||||
|         (capacity, renderer) => new ViewFactory(capacity, renderer), | ||||
|         [VIEW_POOL_CAPACITY, Renderer] | ||||
|       ), | ||||
|       bind(VIEW_POOL_CAPACITY).toValue(10000), | ||||
|       AppViewHydrator, | ||||
|  | ||||
							
								
								
									
										23
									
								
								modules/angular2/src/core/compiler/compiler.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								modules/angular2/src/core/compiler/compiler.js
									
									
									
									
										vendored
									
									
								
							| @ -114,14 +114,21 @@ export class Compiler { | ||||
|     } | ||||
| 
 | ||||
|     var template = this._templateResolver.resolve(component); | ||||
|     var directives = ListWrapper.map( | ||||
|       this._flattenDirectives(template), | ||||
|       (directive) => this._bindDirective(directive) | ||||
|     ); | ||||
|     var renderTemplate = this._buildRenderTemplate(component, template, directives); | ||||
|     pvPromise = this._renderer.compile(renderTemplate).then( (renderPv) => { | ||||
|       return this._compileNestedProtoViews(componentBinding, renderPv, directives, true); | ||||
|     }); | ||||
|     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( | ||||
|         this._flattenDirectives(template), | ||||
|         (directive) => this._bindDirective(directive) | ||||
|       ); | ||||
|       var renderTemplate = this._buildRenderTemplate(component, template, directives); | ||||
|       pvPromise = this._renderer.compile(renderTemplate).then( (renderPv) => { | ||||
|         return this._compileNestedProtoViews(componentBinding, renderPv, directives, true); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     MapWrapper.set(this._compiling, component, pvPromise); | ||||
|     return pvPromise; | ||||
|  | ||||
| @ -70,15 +70,10 @@ export class DynamicComponentLoader { | ||||
| 
 | ||||
|     return this._compiler.compile(type).then(componentProtoView => { | ||||
|       var componentView = this._viewFactory.getView(componentProtoView); | ||||
|       var hostView = location.hostView; | ||||
|       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
 | ||||
|       // from the component child views
 | ||||
|       // See ViewFactory.returnView
 | ||||
|       // See AppViewHydrator.dehydrateDynamicComponentView
 | ||||
|       var dispose = () => {throw "Not implemented";}; | ||||
|       var dispose = () => {throw new BaseException("Not implemented");}; | ||||
|       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 | ||||
|    * 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); | ||||
| 
 | ||||
|     return  this._compiler.compileInHost(type).then(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 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); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
| @ -26,27 +26,21 @@ var _staticKeys; | ||||
|  * @exportedAs angular2/view | ||||
|  */ | ||||
| export class ElementRef { | ||||
|   hostView:viewModule.AppView; | ||||
|   boundElementIndex:number; | ||||
|   injector:Injector; | ||||
|   elementInjector:ElementInjector; | ||||
| 
 | ||||
|   constructor(elementInjector:ElementInjector){ | ||||
|   constructor(elementInjector, hostView, boundElementIndex, injector){ | ||||
|     this.elementInjector = elementInjector; | ||||
|   } | ||||
| 
 | ||||
|   get hostView() { | ||||
|     return this.elementInjector._preBuiltObjects.view; | ||||
|     this.hostView = hostView; | ||||
|     this.boundElementIndex = boundElementIndex; | ||||
|     this.injector = injector; | ||||
|   } | ||||
| 
 | ||||
|   get viewContainer() { | ||||
|     return this.hostView.getOrCreateViewContainer(this.boundElementIndex); | ||||
|   } | ||||
| 
 | ||||
|   get injector() { | ||||
|     return this.elementInjector._lightDomAppInjector; | ||||
|   } | ||||
| 
 | ||||
|   get boundElementIndex() { | ||||
|     return this.elementInjector._proto.index; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class StaticKeys { | ||||
| @ -673,7 +667,7 @@ export class ElementInjector extends TreeNode { | ||||
|   } | ||||
| 
 | ||||
|   getElementRef() { | ||||
|     return new ElementRef(this); | ||||
|     return new ElementRef(this, this._preBuiltObjects.view, this._proto.index, this._lightDomAppInjector); | ||||
|   } | ||||
| 
 | ||||
|   getDynamicallyLoadedComponent() { | ||||
|  | ||||
							
								
								
									
										8
									
								
								modules/angular2/src/core/compiler/view.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								modules/angular2/src/core/compiler/view.js
									
									
									
									
										vendored
									
									
								
							| @ -25,6 +25,9 @@ export class AppView { | ||||
|   elementInjectors:List<ElementInjector>; | ||||
|   changeDetector:ChangeDetector; | ||||
|   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>; | ||||
|   preBuiltObjects: List<PreBuiltObjects>; | ||||
|   proto: AppProtoView; | ||||
| @ -46,7 +49,7 @@ export class AppView { | ||||
|    */ | ||||
|   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.proto = proto; | ||||
|     this.changeDetector = null; | ||||
| @ -59,7 +62,8 @@ export class AppView { | ||||
|     this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this
 | ||||
|     this.renderer = renderer; | ||||
|     this.viewFactory = viewFactory; | ||||
|     this.viewHydrator = viewHydrator; | ||||
|     this.viewHydrator = null; | ||||
|     this.imperativeHostViews = []; | ||||
|   } | ||||
| 
 | ||||
|   init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List, | ||||
|  | ||||
| @ -5,7 +5,6 @@ import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; | ||||
| import {NgElement} from 'angular2/src/core/compiler/ng_element'; | ||||
| import * as viewModule from './view'; | ||||
| 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!
 | ||||
| export const VIEW_POOL_CAPACITY = 'ViewFactory.viewPoolCapacity'; | ||||
| @ -15,13 +14,11 @@ export class ViewFactory { | ||||
|   _poolCapacityPerProtoView:number; | ||||
|   _pooledViewsPerProtoView:Map<viewModule.AppProtoView, List<viewModule.AppView>>; | ||||
|   _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._pooledViewsPerProtoView = MapWrapper.create(); | ||||
|     this._renderer = renderer; | ||||
|     this._viewHydrator = viewHydrator; | ||||
|   } | ||||
| 
 | ||||
|   getView(protoView:viewModule.AppProtoView):viewModule.AppView { | ||||
| @ -48,7 +45,7 @@ export class ViewFactory { | ||||
|   } | ||||
| 
 | ||||
|   _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, | ||||
|       protoView.getVariableBindings(), protoView.getdirectiveRecords()); | ||||
| 
 | ||||
|  | ||||
| @ -7,6 +7,7 @@ import * as viewModule from './view'; | ||||
| import {BindingPropagationConfig, Locals} from 'angular2/change_detection'; | ||||
| 
 | ||||
| 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 | ||||
| @ -27,13 +28,17 @@ import * as renderApi from 'angular2/src/render/api'; | ||||
| @Injectable() | ||||
| export class AppViewHydrator { | ||||
|   _renderer:renderApi.Renderer; | ||||
|   _viewFactory:ViewFactory; | ||||
| 
 | ||||
|   constructor(renderer:renderApi.Renderer) { | ||||
|   constructor(renderer:renderApi.Renderer, viewFactory:ViewFactory) { | ||||
|     this._renderer = renderer; | ||||
|     this._viewFactory = viewFactory; | ||||
|   } | ||||
| 
 | ||||
|   hydrateDynamicComponentView(hostView:viewModule.AppView, boundElementIndex:number, | ||||
|   hydrateDynamicComponentView(location:eli.ElementRef, | ||||
|       componentView:viewModule.AppView, componentDirective:eli.DirectiveBinding, injector:Injector) { | ||||
|     var hostView = location.hostView; | ||||
|     var boundElementIndex = location.boundElementIndex; | ||||
|     var binder = hostView.proto.elementBinders[boundElementIndex]; | ||||
|     if (!binder.hasDynamicComponent()) { | ||||
|       throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`); | ||||
| @ -84,16 +89,23 @@ export class AppViewHydrator { | ||||
|     // 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; | ||||
|     if (isPresent(parentView)) { | ||||
|       // Needed for user views
 | ||||
|       throw new BaseException('Not yet supported'); | ||||
|     if (isPresent(parentComponentLocation)) { | ||||
|       var parentView = parentComponentLocation.hostView.componentChildViews[parentComponentLocation.boundElementIndex]; | ||||
|       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 shadowDomAppInjector = this._createShadowDomAppInjector(binder.componentDirective, injector); | ||||
| 
 | ||||
|     // render views
 | ||||
|     var renderViewRefs = this._renderer.createInPlaceHostView(parentRenderViewRef, hostElementSelector, hostView.proto.render); | ||||
| 
 | ||||
|     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; | ||||
|     if (isPresent(parentView)) { | ||||
|       // Needed for user views
 | ||||
|       throw new BaseException('Not yet supported'); | ||||
|     if (isPresent(parentComponentLocation)) { | ||||
|       var parentView = parentComponentLocation.hostView.componentChildViews[parentComponentLocation.boundElementIndex]; | ||||
|       parentRenderViewRef = parentView.render; | ||||
|       ListWrapper.remove(parentView.imperativeHostViews, hostView); | ||||
|       parentView.changeDetector.removeChild(hostView.changeDetector); | ||||
|     } | ||||
|     var render = hostView.render; | ||||
|     this._viewDehydrateRecurse(hostView); | ||||
| @ -137,7 +151,7 @@ export class AppViewHydrator { | ||||
|       appInjector: Injector, hostElementInjector: eli.ElementInjector, | ||||
|       context: Object, locals:Locals):number { | ||||
|     if (view.hydrated()) throw new BaseException('The view is already hydrated.'); | ||||
| 
 | ||||
|     view.viewHydrator = this; | ||||
|     view.render = renderComponentViewRefs[renderComponentIndex++]; | ||||
| 
 | ||||
|     view.context = context; | ||||
| @ -215,12 +229,22 @@ export class AppViewHydrator { | ||||
|         this._viewDehydrateRecurse(componentView); | ||||
|         var binder = view.proto.elementBinders[i]; | ||||
|         if (binder.hasDynamicComponent()) { | ||||
|           view.componentChildViews[i] = null; | ||||
|           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
 | ||||
|     for (var i = 0; i < view.elementInjectors.length; i++) { | ||||
|       if (isPresent(view.elementInjectors[i])) { | ||||
|  | ||||
| @ -151,7 +151,7 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter { | ||||
|   void appendChild(Node el, Node node) { | ||||
|     el.append(node); | ||||
|   } | ||||
|   void removeChild(Element el, Node node) { | ||||
|   void removeChild(el, Node node) { | ||||
|     node.remove(); | ||||
|   } | ||||
|   void replaceChild(Node el, Node newNode, Node oldNode) { | ||||
|  | ||||
							
								
								
									
										8
									
								
								modules/angular2/src/render/api.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								modules/angular2/src/render/api.js
									
									
									
									
										vendored
									
									
								
							| @ -165,6 +165,14 @@ export class Renderer { | ||||
|    */ | ||||
|   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 | ||||
|    * we don't need to serialize all possible components over the wire, | ||||
|  | ||||
| @ -12,6 +12,7 @@ import {Compiler} from './compiler/compiler'; | ||||
| import {ShadowDomStrategy} from './shadow_dom/shadow_dom_strategy'; | ||||
| import {ProtoViewBuilder} from './view/proto_view_builder'; | ||||
| import {DOM} from 'angular2/src/dom/dom_adapter'; | ||||
| import {ViewContainer} from './view/view_container'; | ||||
| 
 | ||||
| function _resolveViewContainer(vc:api.ViewContainerRef) { | ||||
|   return _resolveView(vc.view).getOrCreateViewContainer(vc.elementIndex); | ||||
| @ -90,6 +91,12 @@ export class DirectDomRenderer extends api.Renderer { | ||||
|     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> { | ||||
|     // Note: compiler already uses a DirectDomProtoViewRef, so we don't
 | ||||
|     // need to do anything here
 | ||||
| @ -156,6 +163,21 @@ export class DirectDomRenderer extends api.Renderer { | ||||
|     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 { | ||||
|     _resolveView(viewRef).setElementProperty(elementIndex, propertyName, propertyValue); | ||||
|   } | ||||
|  | ||||
| @ -11,15 +11,23 @@ export class RenderProtoView { | ||||
|   elementBinders:List<ElementBinder>; | ||||
|   isTemplateElement:boolean; | ||||
|   rootBindingOffset:int; | ||||
|   imperativeRendererId:string; | ||||
| 
 | ||||
|   constructor({ | ||||
|     elementBinders, | ||||
|     element | ||||
|     element, | ||||
|     imperativeRendererId | ||||
|   }) { | ||||
|     this.element = element; | ||||
|     this.elementBinders = elementBinders; | ||||
|     this.isTemplateElement = DOM.isTemplateElement(this.element); | ||||
|     this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0; | ||||
|     this.imperativeRendererId = imperativeRendererId; | ||||
|     if (isPresent(imperativeRendererId)) { | ||||
|       this.rootBindingOffset = 0; | ||||
|       this.isTemplateElement = false; | ||||
|     } else { | ||||
|       this.isTemplateElement = DOM.isTemplateElement(this.element); | ||||
|       this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   mergeChildComponentProtoViews(componentProtoViews:List<RenderProtoView>) { | ||||
|  | ||||
| @ -20,12 +20,18 @@ export class ProtoViewBuilder { | ||||
|   rootElement; | ||||
|   variableBindings: Map<string, string>; | ||||
|   elements:List<ElementBinderBuilder>; | ||||
|   isRootView:boolean; | ||||
|   imperativeRendererId:string; | ||||
| 
 | ||||
|   constructor(rootElement) { | ||||
|     this.rootElement = rootElement; | ||||
|     this.elements = []; | ||||
|     this.variableBindings = MapWrapper.create(); | ||||
|     this.imperativeRendererId = null; | ||||
|   } | ||||
| 
 | ||||
|   setImperativeRendererId(id:string):ProtoViewBuilder { | ||||
|     this.imperativeRendererId = id; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   bindElement(element, description = null):ElementBinderBuilder { | ||||
| @ -90,7 +96,8 @@ export class ProtoViewBuilder { | ||||
|     return new api.ProtoViewDto({ | ||||
|       render: new directDomRenderer.DirectDomProtoViewRef(new RenderProtoView({ | ||||
|         element: this.rootElement, | ||||
|         elementBinders: renderElementBinders | ||||
|         elementBinders: renderElementBinders, | ||||
|         imperativeRendererId: this.imperativeRendererId | ||||
|       })), | ||||
|       elementBinders: apiElementBinders, | ||||
|       variableBindings: this.variableBindings | ||||
|  | ||||
							
								
								
									
										6
									
								
								modules/angular2/src/render/dom/view/view.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								modules/angular2/src/render/dom/view/view.js
									
									
									
									
										vendored
									
									
								
							| @ -31,6 +31,9 @@ export class RenderView { | ||||
|   hydrated: boolean; | ||||
|   _eventDispatcher: any/*EventDispatcher*/; | ||||
|   eventHandlerRemovers: List<Function>; | ||||
|   /// Host views that were added by an imperative view.
 | ||||
|   /// This is a dynamically growing / shrinking array.
 | ||||
|   imperativeHostViews: List<RenderView>; | ||||
| 
 | ||||
|   constructor( | ||||
|       proto:RenderProtoView, rootNodes:List, | ||||
| @ -46,7 +49,8 @@ export class RenderView { | ||||
|     this.componentChildViews = ListWrapper.createFixedSize(boundElements.length); | ||||
|     this.hostLightDom = null; | ||||
|     this.hydrated = false; | ||||
|     this.eventHandlerRemovers = null; | ||||
|     this.eventHandlerRemovers = []; | ||||
|     this.imperativeHostViews = []; | ||||
|   } | ||||
| 
 | ||||
|   getDirectParentLightDom(boundElementIndex:number) { | ||||
|  | ||||
| @ -58,6 +58,12 @@ export class ViewFactory { | ||||
|   } | ||||
| 
 | ||||
|   _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 elementsWithBindingsDynamic; | ||||
|     if (protoView.isTemplateElement) { | ||||
| @ -125,7 +131,7 @@ export class ViewFactory { | ||||
|       // static child components
 | ||||
|       if (binder.hasStaticComponent()) { | ||||
|         var childView = this._createView(binder.nestedProtoView, null); | ||||
|         this.setComponentView(view, binderIdx, childView); | ||||
|         ViewFactory.setComponentView(this._shadowDomStrategy, view, binderIdx, childView); | ||||
|       } | ||||
| 
 | ||||
|       // events
 | ||||
| @ -148,10 +154,10 @@ export class ViewFactory { | ||||
|   // This method is used by the ViewFactory and the ViewHydrator
 | ||||
|   // TODO(tbosch): change shadow dom emulation so that LightDom
 | ||||
|   // 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 lightDom = this._shadowDomStrategy.constructLightDom(hostView, componentView, element); | ||||
|     this._shadowDomStrategy.attachTemplate(element, componentView); | ||||
|     var lightDom = shadowDomStrategy.constructLightDom(hostView, componentView, element); | ||||
|     shadowDomStrategy.attachTemplate(element, componentView); | ||||
|     hostView.lightDoms[elementIndex] = lightDom; | ||||
|     hostView.componentChildViews[elementIndex] = componentView; | ||||
|   } | ||||
|  | ||||
| @ -7,6 +7,7 @@ import {EventManager} from '../events/event_manager'; | ||||
| import {ViewFactory} from './view_factory'; | ||||
| import * as vcModule from './view_container'; | ||||
| 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 | ||||
| @ -23,14 +24,16 @@ import * as viewModule from './view'; | ||||
| export class RenderViewHydrator { | ||||
|   _eventManager:EventManager; | ||||
|   _viewFactory:ViewFactory; | ||||
|   _shadowDomStrategy:ShadowDomStrategy; | ||||
| 
 | ||||
|   constructor(eventManager:EventManager, viewFactory:ViewFactory) { | ||||
|   constructor(eventManager:EventManager, viewFactory:ViewFactory, shadowDomStrategy:ShadowDomStrategy) { | ||||
|     this._eventManager = eventManager; | ||||
|     this._viewFactory = viewFactory; | ||||
|     this._shadowDomStrategy = shadowDomStrategy; | ||||
|   } | ||||
| 
 | ||||
|   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]; | ||||
|     this._viewHydrateRecurse(componentView, lightDom); | ||||
|     if (isPresent(lightDom)) { | ||||
| @ -50,15 +53,17 @@ export class RenderViewHydrator { | ||||
| 
 | ||||
|   hydrateInPlaceHostView(parentView:viewModule.RenderView, hostView:viewModule.RenderView) { | ||||
|     if (isPresent(parentView)) { | ||||
|       throw new BaseException('Not supported yet'); | ||||
|       ListWrapper.push(parentView.imperativeHostViews, hostView); | ||||
|     } | ||||
|     this._viewHydrateRecurse(hostView, null); | ||||
|   } | ||||
| 
 | ||||
|   dehydrateInPlaceHostView(parentView:viewModule.RenderView, hostView:viewModule.RenderView) { | ||||
|     if (isPresent(parentView)) { | ||||
|       throw new BaseException('Not supported yet'); | ||||
|       ListWrapper.remove(parentView.imperativeHostViews, hostView); | ||||
|     } | ||||
|     vcModule.ViewContainer.removeViewNodes(hostView); | ||||
|     hostView.rootNodes = []; | ||||
|     this._viewDehydrateRecurse(hostView); | ||||
|   } | ||||
| 
 | ||||
| @ -130,12 +135,24 @@ export class RenderViewHydrator { | ||||
|         this._viewDehydrateRecurse(cv); | ||||
|         if (view.proto.elementBinders[i].hasDynamicComponent()) { | ||||
|           vcModule.ViewContainer.removeViewNodes(cv); | ||||
|           this._viewFactory.returnView(cv); | ||||
|           view.lightDoms[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
 | ||||
|     if (isPresent(view.viewContainers)) { | ||||
|       for (var i = 0; i < view.viewContainers.length; i++) { | ||||
|  | ||||
| @ -79,6 +79,7 @@ function _getAppBindings() { | ||||
|     bind(ShadowDomStrategy).toFactory( | ||||
|         (styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head), | ||||
|         [StyleUrlResolver, appDocumentToken]), | ||||
|     bind(DirectDomRenderer).toClass(DirectDomRenderer), | ||||
|     bind(Renderer).toClass(DirectDomRenderer), | ||||
|     bind(rc.Compiler).toClass(rc.DefaultCompiler), | ||||
|     rvf.ViewFactory, | ||||
|  | ||||
| @ -10,10 +10,11 @@ import { | ||||
|   inject, | ||||
|   IS_DARTIUM, | ||||
|   it, | ||||
|   SpyObject, proxy | ||||
| } from 'angular2/test_lib'; | ||||
| 
 | ||||
| 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 {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 * 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() { | ||||
|   describe('compiler', function() { | ||||
|     var reader, tplResolver, renderer, protoViewFactory, cmpUrlMapper; | ||||
|     var reader, tplResolver, renderer, protoViewFactory, cmpUrlMapper, renderCompileRequests; | ||||
| 
 | ||||
|     beforeEach(() => { | ||||
|       reader = new DirectiveMetadataReader(); | ||||
|       tplResolver = new FakeTemplateResolver(); | ||||
|       cmpUrlMapper = new RuntimeComponentUrlMapper(); | ||||
|       renderer = new SpyRenderer(); | ||||
|     }); | ||||
| 
 | ||||
|     function createCompiler(renderCompileResults:List, protoViewFactoryResults:List<AppProtoView>) { | ||||
|       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) | ||||
|       return new Compiler( | ||||
|         reader, | ||||
| @ -62,8 +71,8 @@ export function main() { | ||||
|         tplResolver.setView(MainComponent, template); | ||||
|         var compiler = createCompiler([createRenderProtoView()], [createProtoView()]); | ||||
|         return compiler.compile(MainComponent).then( (protoView) => { | ||||
|           expect(renderer.requests.length).toBe(1); | ||||
|           return renderer.requests[0]; | ||||
|           expect(renderCompileRequests.length).toBe(1); | ||||
|           return renderCompileRequests[0]; | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
| @ -362,6 +371,11 @@ export function main() { | ||||
|     })); | ||||
| 
 | ||||
|     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>'})); | ||||
|       var rootProtoView = createProtoView([ | ||||
|         createComponentElementBinder(reader, MainComponent) | ||||
| @ -379,6 +393,25 @@ export function main() { | ||||
|         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) {} | ||||
| } | ||||
| 
 | ||||
| class FakeRenderer extends renderApi.Renderer { | ||||
|   requests:List<renderApi.ViewDefinition>; | ||||
|   _results:List; | ||||
| 
 | ||||
|   constructor(results) { | ||||
|     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)]) | ||||
|     ); | ||||
|   } | ||||
| @proxy | ||||
| @IMPLEMENTS(Renderer) | ||||
| class SpyRenderer extends SpyObject { | ||||
|   constructor(){super(Renderer);} | ||||
|   noSuchMethod(m){return super.noSuchMethod(m)} | ||||
| } | ||||
| 
 | ||||
| class FakeUrlResolver extends UrlResolver { | ||||
|  | ||||
| @ -21,6 +21,7 @@ import {View} from 'angular2/src/core/annotations/view'; | ||||
| import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; | ||||
| import {ElementRef} from 'angular2/src/core/compiler/element_injector'; | ||||
| import {If} from 'angular2/src/directives/if'; | ||||
| import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer'; | ||||
| 
 | ||||
| export function main() { | ||||
|   describe('DynamicComponentLoader', function () { | ||||
| @ -134,9 +135,71 @@ 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'; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class DynamicallyCreatedComponentService { | ||||
| } | ||||
|  | ||||
| @ -714,7 +714,7 @@ export function main() { | ||||
|       beforeEach( () => { | ||||
|         renderer = new FakeRenderer(); | ||||
|         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(); | ||||
|       }); | ||||
| 
 | ||||
|  | ||||
| @ -33,6 +33,9 @@ import {If} from 'angular2/src/directives/if'; | ||||
| 
 | ||||
| import {ViewContainer} from 'angular2/src/core/compiler/view_container'; | ||||
| 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() { | ||||
|   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:
 | ||||
|     // - https://github.com/angular/angular/issues/776
 | ||||
|     // - 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({ | ||||
|   selector: 'dynamic-vp' | ||||
| }) | ||||
| @ -888,7 +918,7 @@ class ComponentWithPipes { | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'child-cmp', | ||||
|   injectables: [MyService] | ||||
|   injectables: [MyService], | ||||
| }) | ||||
| @View({ | ||||
|   directives: [MyDir], | ||||
|  | ||||
| @ -35,7 +35,7 @@ export function main() { | ||||
|     }); | ||||
| 
 | ||||
|     function createViewFactory({capacity}):ViewFactory { | ||||
|       return new ViewFactory(capacity, renderer, null); | ||||
|       return new ViewFactory(capacity, renderer); | ||||
|     } | ||||
| 
 | ||||
|     function createProtoChangeDetector() { | ||||
|  | ||||
| @ -21,21 +21,24 @@ import {AppProtoView, AppView} from 'angular2/src/core/compiler/view'; | ||||
| import {Renderer, ViewRef} from 'angular2/src/render/api'; | ||||
| import {ChangeDetector} from 'angular2/change_detection'; | ||||
| 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 {Component} from 'angular2/src/core/annotations/annotations'; | ||||
| import {AppViewHydrator} from 'angular2/src/core/compiler/view_hydrator'; | ||||
| import {ViewFactory} from 'angular2/src/core/compiler/view_factory'; | ||||
| 
 | ||||
| export function main() { | ||||
|   describe('AppViewHydrator', () => { | ||||
|     var renderer; | ||||
|     var reader; | ||||
|     var hydrator; | ||||
|     var viewFactory; | ||||
| 
 | ||||
|     beforeEach( () => { | ||||
|       renderer = new SpyRenderer(); | ||||
|       reader = new DirectiveMetadataReader(); | ||||
|       hydrator = new AppViewHydrator(renderer); | ||||
|       viewFactory = new SpyViewFactory(); | ||||
|       hydrator = new AppViewHydrator(renderer, viewFactory); | ||||
|     }); | ||||
| 
 | ||||
|     function createDirectiveBinding(type) { | ||||
| @ -81,14 +84,14 @@ export function main() { | ||||
|     } | ||||
| 
 | ||||
|     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(); | ||||
|       view.init(changeDetector, [], [], [], []); | ||||
|       return view; | ||||
|     } | ||||
| 
 | ||||
|     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 eis; | ||||
| @ -117,7 +120,7 @@ export function main() { | ||||
|         var view = createHostView(pv, null, null); | ||||
|         var shadowView = createEmptyView(); | ||||
|         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'); | ||||
|       }); | ||||
| 
 | ||||
| @ -126,7 +129,7 @@ export function main() { | ||||
|         var view = createHostView(pv, null, null); | ||||
|         var shadowView = createEmptyView(); | ||||
|         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'); | ||||
|       }); | ||||
| 
 | ||||
| @ -135,9 +138,10 @@ export function main() { | ||||
|         var shadowView = createEmptyView(); | ||||
|         var view = createHostView(pv, null, null); | ||||
|         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( | ||||
|           () => hydrator.hydrateDynamicComponentView(view, 0, shadowView, null, null) | ||||
|           () => hydrator.hydrateDynamicComponentView(elRef, shadowView, null, null) | ||||
|         ).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.changeDetector.spy('removeShadowDomChild')).not.toHaveBeenCalled(); | ||||
|         expect(viewFactory.spy('returnView')).not.toHaveBeenCalled(); | ||||
|       }); | ||||
| 
 | ||||
|       it('should clear dynamic child components', () => { | ||||
| @ -225,6 +230,19 @@ export function main() { | ||||
| 
 | ||||
|         expect(hostView.componentChildViews[0]).toBe(null); | ||||
|         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);} | ||||
|   noSuchMethod(m){return super.noSuchMethod(m)} | ||||
| } | ||||
| 
 | ||||
| @proxy | ||||
| @IMPLEMENTS(ViewFactory) | ||||
| class SpyViewFactory extends SpyObject { | ||||
|   constructor(){super(ViewFactory);} | ||||
|   noSuchMethod(m){return super.noSuchMethod(m)} | ||||
| } | ||||
| @ -57,7 +57,7 @@ export function main() { | ||||
|     } | ||||
| 
 | ||||
|     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 eij = createElementInjector(); | ||||
|       view.init(changeDetector, [eij], [eij], | ||||
|  | ||||
| @ -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) => { | ||||
|       createRenderer(); | ||||
|       renderer.createHostProtoView('someComponentId').then( (rootProtoView) => { | ||||
|  | ||||
| @ -46,7 +46,7 @@ export class IntegrationTestbed { | ||||
|     this.eventPlugin = new FakeEventManagerPlugin(); | ||||
|     var eventManager = new EventManager([this.eventPlugin], new FakeVmTurnZone()); | ||||
|     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); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -14,7 +14,7 @@ import { | ||||
|   xit, | ||||
|   SpyObject, proxy | ||||
| } 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 {ElementBinder} from 'angular2/src/render/dom/view/element_binder'; | ||||
| @ -76,7 +76,7 @@ export function main() { | ||||
|     function createHostView(pv, shadowDomView) { | ||||
|       var view = new RenderView(pv, [el('<div></div>')], | ||||
|         [], [el('<div></div>')], [null]); | ||||
|       viewFactory.setComponentView(view, 0, shadowDomView); | ||||
|       ViewFactory.setComponentView(shadowDomStrategy, view, 0, shadowDomView); | ||||
|       return view; | ||||
|     } | ||||
| 
 | ||||
| @ -94,8 +94,8 @@ export function main() { | ||||
|       shadowDomStrategy.spy('constructLightDom').andCallFake( (lightDomView, shadowDomView, el) => { | ||||
|         return new SpyLightDom(); | ||||
|       }); | ||||
|       viewFactory = new ViewFactory(1, eventManager, shadowDomStrategy); | ||||
|       viewHydrator = new RenderViewHydrator(eventManager, viewFactory); | ||||
|       viewFactory = new SpyViewFactory(); | ||||
|       viewHydrator = new RenderViewHydrator(eventManager, viewFactory, shadowDomStrategy); | ||||
|     }); | ||||
| 
 | ||||
|     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', () => { | ||||
| 
 | ||||
|       it('should hydrate existing child components', () => { | ||||
| @ -128,9 +181,12 @@ export function main() { | ||||
|     describe('dehydrate... shared functionality', () => { | ||||
|       var hostView; | ||||
| 
 | ||||
|       function createAndHydrate(nestedProtoView, shadowView) { | ||||
|       function createAndHydrate(nestedProtoView, shadowView, imperativeHostView = null) { | ||||
|         var hostPv = createHostProtoView(nestedProtoView); | ||||
|         hostView = createHostView(hostPv, shadowView); | ||||
|         if (isPresent(imperativeHostView)) { | ||||
|           viewHydrator.hydrateInPlaceHostView(hostView, imperativeHostView); | ||||
|         } | ||||
| 
 | ||||
|         hydrate(hostView); | ||||
|       } | ||||
| @ -152,15 +208,36 @@ export function main() { | ||||
| 
 | ||||
|         expect(hostView.componentChildViews[0]).toBe(shadowView); | ||||
|         expect(shadowView.rootNodes[0].parentNode).toBeTruthy(); | ||||
|         expect(viewFactory.spy('returnView')).not.toHaveBeenCalled(); | ||||
|       }); | ||||
| 
 | ||||
|       it('should clear dynamic child components', () => { | ||||
|         var shadowView = createEmptyView(); | ||||
|         createAndHydrate(null, shadowView); | ||||
|         expect(shadowView.rootNodes[0].parentNode).toBeTruthy(); | ||||
| 
 | ||||
|         dehydrate(hostView); | ||||
| 
 | ||||
|         expect(hostView.componentChildViews[0]).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)} | ||||
| } | ||||
| 
 | ||||
| @proxy | ||||
| @IMPLEMENTS(ViewFactory) | ||||
| class SpyViewFactory extends SpyObject { | ||||
|   constructor(){super(ViewFactory);} | ||||
|   noSuchMethod(m){return super.noSuchMethod(m)} | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user