UNPKG

angular2

Version:

Angular 2 - a web framework for modern web apps

303 lines (265 loc) 12.3 kB
import {Injectable, Inject, OpaqueToken, Injector} from 'angular2/di'; import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; import * as eli from './element_injector'; import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; import * as vcModule from './view_container'; 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 * the view tree, without incurring the cost of recreating the underlying * injectors and watch records. * * A dehydrated view has the following properties: * * - all element injectors are empty. * - all appInjectors are released. * - all viewcontainers are empty. * - all context locals are set to null. * - the view context is null. * * A call to hydrate/dehydrate is called whenever a view is attached/detached, * but it does not do the attach/detach itself. */ @Injectable() export class AppViewHydrator { _renderer:renderApi.Renderer; _viewFactory:ViewFactory; constructor(renderer:renderApi.Renderer, viewFactory:ViewFactory) { this._renderer = renderer; this._viewFactory = viewFactory; } 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}`); } if (isPresent(hostView.componentChildViews[boundElementIndex])) { throw new BaseException(`There already is a bound component at element ${boundElementIndex}`); } var hostElementInjector = hostView.elementInjectors[boundElementIndex]; if (isBlank(injector)) { injector = hostElementInjector.getLightDomAppInjector(); } // shadowDomAppInjector var shadowDomAppInjector = this._createShadowDomAppInjector(componentDirective, injector); // Needed to make rtts-assert happy in unit tests... if (isBlank(shadowDomAppInjector)) { shadowDomAppInjector = null; } // create component instance var component = hostElementInjector.dynamicallyCreateComponent(componentDirective, shadowDomAppInjector); // componentView hostView.componentChildViews[boundElementIndex] = componentView; hostView.changeDetector.addShadowDomChild(componentView.changeDetector); // render views var renderViewRefs = this._renderer.createDynamicComponentView(hostView.render, boundElementIndex, componentView.proto.render); this._viewHydrateRecurse( componentView, renderViewRefs, 0, shadowDomAppInjector, hostElementInjector, component, null ); } dehydrateDynamicComponentView(parentView:viewModule.AppView, boundElementIndex:number) { throw new BaseException('Not yet implemented!'); // Something along these lines: // var binder = parentView.proto.elementBinders[boundElementIndex]; // if (!binder.hasDynamicComponent()) { // throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`); // } // var componentView = parentView.componentChildViews[boundElementIndex]; // if (isBlank(componentView)) { // throw new BaseException(`There is no bound component at element ${boundElementIndex}`); // } // this._viewDehydrateRecurse(componentView); // parentView.changeDetector.removeShadowDomChild(componentView.changeDetector); // this._renderer.destroyDynamicComponentChildView(parentView.render, boundElementIndex); // parentView.componentChildViews[boundElementIndex] = null; } hydrateInPlaceHostView(parentComponentLocation:eli.ElementRef, hostElementSelector, hostView:viewModule.AppView, injector:Injector) { var parentRenderViewRef = null; 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); var renderViewRefs = this._renderer.createInPlaceHostView(parentRenderViewRef, hostElementSelector, hostView.proto.render); this._viewHydrateRecurse( hostView, renderViewRefs, 0, shadowDomAppInjector, null, new Object(), null ); } dehydrateInPlaceHostView(parentComponentLocation:eli.ElementRef, hostView:viewModule.AppView) { var parentRenderViewRef = null; 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); this._renderer.destroyInPlaceHostView(parentRenderViewRef, render); } hydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, atIndex:number, view:viewModule.AppView, injector:Injector = null) { if (!viewContainer.hydrated()) throw new BaseException( 'Cannot create views on a dehydrated ViewContainer'); if (isBlank(injector)) { injector = viewContainer.elementInjector.getLightDomAppInjector(); } var renderViewRefs = this._renderer.createViewInContainer(viewContainer.getRender(), atIndex, view.proto.render); viewContainer.parentView.changeDetector.addChild(view.changeDetector); this._viewHydrateRecurse(view, renderViewRefs, 0, injector, viewContainer.elementInjector.getHost(), viewContainer.parentView.context, viewContainer.parentView.locals); } dehydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, atIndex:number, view:viewModule.AppView) { view.changeDetector.remove(); this._viewDehydrateRecurse(view); this._renderer.destroyViewInContainer(viewContainer.getRender(), atIndex); } _viewHydrateRecurse( view:viewModule.AppView, renderComponentViewRefs:List<renderApi.ViewRef>, renderComponentIndex:number, 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; view.locals.parent = locals; var binders = view.proto.elementBinders; for (var i = 0; i < binders.length; ++i) { var componentDirective = binders[i].componentDirective; var shadowDomAppInjector = null; // shadowDomAppInjector if (isPresent(componentDirective)) { shadowDomAppInjector = this._createShadowDomAppInjector(componentDirective, appInjector); } else { shadowDomAppInjector = null; } // elementInjectors var elementInjector = view.elementInjectors[i]; if (isPresent(elementInjector)) { elementInjector.instantiateDirectives(appInjector, hostElementInjector, shadowDomAppInjector, view.preBuiltObjects[i]); this._setUpEventEmitters(view, elementInjector, i); // The exporting of $implicit is a special case. Since multiple elements will all export // the different values as $implicit, directly assign $implicit bindings to the variable // name. var exportImplicitName = elementInjector.getExportImplicitName(); if (elementInjector.isExportingComponent()) { view.locals.set(exportImplicitName, elementInjector.getComponent()); } else if (elementInjector.isExportingElement()) { view.locals.set(exportImplicitName, elementInjector.getNgElement().domElement); } } if (binders[i].hasStaticComponent()) { renderComponentIndex = this._viewHydrateRecurse( view.componentChildViews[i], renderComponentViewRefs, renderComponentIndex, shadowDomAppInjector, elementInjector, elementInjector.getComponent(), null ); } } view.changeDetector.hydrate(view.context, view.locals, view); view.renderer.setEventDispatcher(view.render, view); return renderComponentIndex; } _setUpEventEmitters(view:viewModule.AppView, elementInjector:eli.ElementInjector, boundElementIndex:number) { var emitters = elementInjector.getEventEmitterAccessors(); for(var directiveIndex = 0; directiveIndex < emitters.length; ++directiveIndex) { var directiveEmitters = emitters[directiveIndex]; var directive = elementInjector.getDirectiveAtIndex(directiveIndex); for (var eventIndex = 0; eventIndex < directiveEmitters.length; ++eventIndex) { var eventEmitterAccessor = directiveEmitters[eventIndex]; eventEmitterAccessor.subscribe(view, boundElementIndex, directive); } } } /** * This should only be called by View or ViewContainer. */ _viewDehydrateRecurse(view:viewModule.AppView) { // Note: preserve the opposite order of the hydration process. // componentChildViews for (var i = 0; i < view.componentChildViews.length; i++) { var componentView = view.componentChildViews[i]; if (isPresent(componentView)) { this._viewDehydrateRecurse(componentView); var binder = view.proto.elementBinders[i]; if (binder.hasDynamicComponent()) { 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])) { view.elementInjectors[i].clearDirectives(); } } // viewContainers if (isPresent(view.viewContainers)) { for (var i = 0; i < view.viewContainers.length; i++) { var vc = view.viewContainers[i]; if (isPresent(vc)) { this._viewContainerDehydrateRecurse(vc); } } } view.render = null; if (isPresent(view.locals)) { view.locals.clearValues(); } view.context = null; view.changeDetector.dehydrate(); } _createShadowDomAppInjector(componentDirective, appInjector) { var shadowDomAppInjector = null; // shadowDomAppInjector var injectables = componentDirective.resolvedInjectables; if (isPresent(injectables)) { shadowDomAppInjector = appInjector.createChildFromResolved(injectables); } else { shadowDomAppInjector = appInjector; } return shadowDomAppInjector; } /** * This should only be called by View or ViewContainer. */ _viewContainerDehydrateRecurse(viewContainer:vcModule.ViewContainer) { for (var i=0; i<viewContainer.length; i++) { var view = viewContainer.get(i); view.changeDetector.remove(); this._viewDehydrateRecurse(view); } // Note: We don't call clear here, // as we don't want to change the render side // as the render side does its own recursion. viewContainer.internalClearWithoutRender(); } }