UNPKG

angular2

Version:

Angular 2 - a web framework for modern web apps

461 lines (421 loc) 19.8 kB
import { Injector, Inject, Provider, Injectable, ResolvedProvider, forwardRef } from 'angular2/src/core/di'; import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {BaseException} from 'angular2/src/facade/exceptions'; import * as viewModule from './view'; import {ElementRef, ElementRef_} from './element_ref'; import {ProtoViewRef, ViewRef, HostViewRef, internalView, internalProtoView} from './view_ref'; import {ViewContainerRef} from './view_container_ref'; import {TemplateRef, TemplateRef_} from './template_ref'; import { Renderer, RenderViewRef, RenderFragmentRef, RenderViewWithFragments } from 'angular2/src/core/render/api'; import {AppViewManagerUtils} from './view_manager_utils'; import {AppViewPool} from './view_pool'; import {AppViewListener} from './view_listener'; import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile'; import {ProtoViewFactory} from './proto_view_factory'; /** * Service exposing low level API for creating, moving and destroying Views. * * Most applications should use higher-level abstractions like {@link DynamicComponentLoader} and * {@link ViewContainerRef} instead. */ export abstract class AppViewManager { /** * Returns a {@link ViewContainerRef} of the View Container at the specified location. */ abstract getViewContainer(location: ElementRef): ViewContainerRef; /** * Returns the {@link ElementRef} that makes up the specified Host View. */ getHostElement(hostViewRef: HostViewRef): ElementRef { var hostView = internalView(<ViewRef>hostViewRef); if (hostView.proto.type !== viewModule.ViewType.HOST) { throw new BaseException('This operation is only allowed on host views'); } return hostView.elementRefs[hostView.elementOffset]; } /** * Searches the Component View of the Component specified via `hostLocation` and returns the * {@link ElementRef} for the Element identified via a Variable Name `variableName`. * * Throws an exception if the specified `hostLocation` is not a Host Element of a Component, or if * variable `variableName` couldn't be found in the Component View of this Component. */ abstract getNamedElementInComponentView(hostLocation: ElementRef, variableName: string): ElementRef; /** * Returns the component instance for the provided Host Element. */ abstract getComponent(hostLocation: ElementRef): any; /** * Creates an instance of a Component and attaches it to the first element in the global View * (usually DOM Document) that matches the component's selector or `overrideSelector`. * * This as a low-level way to bootstrap an application and upgrade an existing Element to a * Host Element. Most applications should use {@link DynamicComponentLoader#loadAsRoot} instead. * * The Component and its View are created based on the `hostProtoViewRef` which can be obtained * by compiling the component with {@link Compiler#compileInHost}. * * Use {@link AppViewManager#destroyRootHostView} to destroy the created Component and it's Host * View. * * ### Example * * ``` * @ng.Component({ * selector: 'child-component' * }) * @ng.View({ * template: 'Child' * }) * class ChildComponent { * * } * * @ng.Component({ * selector: 'my-app' * }) * @ng.View({ * template: ` * Parent (<some-component></some-component>) * ` * }) * class MyApp implements OnDestroy { * viewRef: ng.ViewRef; * * constructor(public appViewManager: ng.AppViewManager, compiler: ng.Compiler) { * compiler.compileInHost(ChildComponent).then((protoView: ng.ProtoViewRef) => { * this.viewRef = appViewManager.createRootHostView(protoView, 'some-component', null); * }) * } * * ngOnDestroy() { * this.appViewManager.destroyRootHostView(this.viewRef); * this.viewRef = null; * } * } * * ng.bootstrap(MyApp); * ``` */ abstract createRootHostView(hostProtoViewRef: ProtoViewRef, overrideSelector: string, injector: Injector): HostViewRef; /** * Destroys the Host View created via {@link AppViewManager#createRootHostView}. * * Along with the Host View, the Component Instance as well as all nested View and Components are * destroyed as well. */ abstract destroyRootHostView(hostViewRef: HostViewRef); /** * Instantiates an Embedded View based on the {@link TemplateRef `templateRef`} and inserts it * into the View Container specified via `viewContainerLocation` at the specified `index`. * * Returns the {@link ViewRef} for the newly created View. * * This as a low-level way to create and attach an Embedded via to a View Container. Most * applications should used {@link ViewContainerRef#createEmbeddedView} instead. * * Use {@link AppViewManager#destroyViewInContainer} to destroy the created Embedded View. */ // TODO(i): this low-level version of ViewContainerRef#createEmbeddedView doesn't add anything new // we should make it private, otherwise we have two apis to do the same thing. abstract createEmbeddedViewInContainer(viewContainerLocation: ElementRef, index: number, templateRef: TemplateRef): ViewRef; /** * Instantiates a single {@link Component} and inserts its Host View into the View Container * found at `viewContainerLocation`. Within the container, the view will be inserted at position * specified via `index`. * * The component is instantiated using its {@link ProtoViewRef `protoViewRef`} which can be * obtained via {@link Compiler#compileInHost}. * * You can optionally specify `imperativelyCreatedInjector`, which configure the {@link Injector} * that will be created for the Host View. * * Returns the {@link HostViewRef} of the Host View created for the newly instantiated Component. * * Use {@link AppViewManager#destroyViewInContainer} to destroy the created Host View. */ abstract createHostViewInContainer(viewContainerLocation: ElementRef, index: number, protoViewRef: ProtoViewRef, imperativelyCreatedInjector: ResolvedProvider[]): HostViewRef; /** * Destroys an Embedded or Host View attached to a View Container at the specified `index`. * * The View Container is located via `viewContainerLocation`. */ abstract destroyViewInContainer(viewContainerLocation: ElementRef, index: number); /** * * See {@link AppViewManager#detachViewInContainer}. */ // TODO(i): refactor detachViewInContainer+attachViewInContainer to moveViewInContainer abstract attachViewInContainer(viewContainerLocation: ElementRef, index: number, viewRef: ViewRef): ViewRef; /** * See {@link AppViewManager#attachViewInContainer}. */ abstract detachViewInContainer(viewContainerLocation: ElementRef, index: number): ViewRef; } @Injectable() export class AppViewManager_ extends AppViewManager { private _protoViewFactory: ProtoViewFactory; constructor(private _viewPool: AppViewPool, private _viewListener: AppViewListener, private _utils: AppViewManagerUtils, private _renderer: Renderer, @Inject(forwardRef(() => ProtoViewFactory)) _protoViewFactory) { super(); this._protoViewFactory = _protoViewFactory; } getViewContainer(location: ElementRef): ViewContainerRef { var hostView = internalView((<ElementRef_>location).parentView); return hostView.elementInjectors[(<ElementRef_>location).boundElementIndex] .getViewContainerRef(); } getNamedElementInComponentView(hostLocation: ElementRef, variableName: string): ElementRef { var hostView = internalView((<ElementRef_>hostLocation).parentView); var boundElementIndex = (<ElementRef_>hostLocation).boundElementIndex; var componentView = hostView.getNestedView(boundElementIndex); if (isBlank(componentView)) { throw new BaseException(`There is no component directive at element ${boundElementIndex}`); } var binderIdx = componentView.proto.variableLocations.get(variableName); if (isBlank(binderIdx)) { throw new BaseException(`Could not find variable ${variableName}`); } return componentView.elementRefs[componentView.elementOffset + binderIdx]; } getComponent(hostLocation: ElementRef): any { var hostView = internalView((<ElementRef_>hostLocation).parentView); var boundElementIndex = (<ElementRef_>hostLocation).boundElementIndex; return this._utils.getComponentInstance(hostView, boundElementIndex); } /** @internal */ _createRootHostViewScope: WtfScopeFn = wtfCreateScope('AppViewManager#createRootHostView()'); createRootHostView(hostProtoViewRef: ProtoViewRef, overrideSelector: string, injector: Injector): HostViewRef { var s = this._createRootHostViewScope(); var hostProtoView: viewModule.AppProtoView = internalProtoView(hostProtoViewRef); this._protoViewFactory.initializeProtoViewIfNeeded(hostProtoView); var hostElementSelector = overrideSelector; if (isBlank(hostElementSelector)) { hostElementSelector = hostProtoView.elementBinders[0].componentDirective.metadata.selector; } var renderViewWithFragments = this._renderer.createRootHostView( hostProtoView.render, hostProtoView.mergeInfo.embeddedViewCount + 1, hostElementSelector); var hostView = this._createMainView(hostProtoView, renderViewWithFragments); this._renderer.hydrateView(hostView.render); this._utils.hydrateRootHostView(hostView, injector); return wtfLeave(s, hostView.ref); } /** @internal */ _destroyRootHostViewScope: WtfScopeFn = wtfCreateScope('AppViewManager#destroyRootHostView()'); destroyRootHostView(hostViewRef: HostViewRef) { // Note: Don't put the hostView into the view pool // as it is depending on the element for which it was created. var s = this._destroyRootHostViewScope(); var hostView = internalView(<ViewRef>hostViewRef); this._renderer.detachFragment(hostView.renderFragment); this._renderer.dehydrateView(hostView.render); this._viewDehydrateRecurse(hostView); this._viewListener.onViewDestroyed(hostView); this._renderer.destroyView(hostView.render); wtfLeave(s); } /** @internal */ _createEmbeddedViewInContainerScope: WtfScopeFn = wtfCreateScope('AppViewManager#createEmbeddedViewInContainer()'); createEmbeddedViewInContainer(viewContainerLocation: ElementRef, index: number, templateRef: TemplateRef): ViewRef { var s = this._createEmbeddedViewInContainerScope(); var protoView = internalProtoView((<TemplateRef_>templateRef).protoViewRef); if (protoView.type !== viewModule.ViewType.EMBEDDED) { throw new BaseException('This method can only be called with embedded ProtoViews!'); } this._protoViewFactory.initializeProtoViewIfNeeded(protoView); return wtfLeave(s, this._createViewInContainer(viewContainerLocation, index, protoView, templateRef.elementRef, null)); } /** @internal */ _createHostViewInContainerScope: WtfScopeFn = wtfCreateScope('AppViewManager#createHostViewInContainer()'); createHostViewInContainer(viewContainerLocation: ElementRef, index: number, protoViewRef: ProtoViewRef, imperativelyCreatedInjector: ResolvedProvider[]): HostViewRef { var s = this._createHostViewInContainerScope(); var protoView = internalProtoView(protoViewRef); if (protoView.type !== viewModule.ViewType.HOST) { throw new BaseException('This method can only be called with host ProtoViews!'); } this._protoViewFactory.initializeProtoViewIfNeeded(protoView); return wtfLeave( s, this._createViewInContainer(viewContainerLocation, index, protoView, viewContainerLocation, imperativelyCreatedInjector)); } /** * * See {@link AppViewManager#destroyViewInContainer}. * @internal */ _createViewInContainer(viewContainerLocation: ElementRef, index: number, protoView: viewModule.AppProtoView, context: ElementRef, imperativelyCreatedInjector: ResolvedProvider[]): ViewRef { var parentView = internalView((<ElementRef_>viewContainerLocation).parentView); var boundElementIndex = (<ElementRef_>viewContainerLocation).boundElementIndex; var contextView = internalView((<ElementRef_>context).parentView); var contextBoundElementIndex = (<ElementRef_>context).boundElementIndex; var embeddedFragmentView = contextView.getNestedView(contextBoundElementIndex); var view; if (protoView.type === viewModule.ViewType.EMBEDDED && isPresent(embeddedFragmentView) && !embeddedFragmentView.hydrated()) { // Case 1: instantiate the first view of a template that has been merged into a parent view = embeddedFragmentView; this._attachRenderView(parentView, boundElementIndex, index, view); } else { // Case 2: instantiate another copy of the template or a host ProtoView. // This is a separate case // as we only inline one copy of the template into the parent view. view = this._createPooledView(protoView); this._attachRenderView(parentView, boundElementIndex, index, view); this._renderer.hydrateView(view.render); } this._utils.attachViewInContainer(parentView, boundElementIndex, contextView, contextBoundElementIndex, index, view); this._utils.hydrateViewInContainer(parentView, boundElementIndex, contextView, contextBoundElementIndex, index, imperativelyCreatedInjector); return view.ref; } /** @internal */ _attachRenderView(parentView: viewModule.AppView, boundElementIndex: number, index: number, view: viewModule.AppView) { var elementRef = parentView.elementRefs[boundElementIndex]; if (index === 0) { this._renderer.attachFragmentAfterElement(elementRef, view.renderFragment); } else { var prevView = parentView.viewContainers[boundElementIndex].views[index - 1]; this._renderer.attachFragmentAfterFragment(prevView.renderFragment, view.renderFragment); } } /** @internal */ _destroyViewInContainerScope = wtfCreateScope('AppViewMananger#destroyViewInContainer()'); destroyViewInContainer(viewContainerLocation: ElementRef, index: number) { var s = this._destroyViewInContainerScope(); var parentView = internalView((<ElementRef_>viewContainerLocation).parentView); var boundElementIndex = (<ElementRef_>viewContainerLocation).boundElementIndex; this._destroyViewInContainer(parentView, boundElementIndex, index); wtfLeave(s); } /** @internal */ _attachViewInContainerScope = wtfCreateScope('AppViewMananger#attachViewInContainer()'); // TODO(i): refactor detachViewInContainer+attachViewInContainer to moveViewInContainer attachViewInContainer(viewContainerLocation: ElementRef, index: number, viewRef: ViewRef): ViewRef { var s = this._attachViewInContainerScope(); var view = internalView(viewRef); var parentView = internalView((<ElementRef_>viewContainerLocation).parentView); var boundElementIndex = (<ElementRef_>viewContainerLocation).boundElementIndex; // TODO(tbosch): the public methods attachViewInContainer/detachViewInContainer // are used for moving elements without the same container. // We will change this into an atomic `move` operation, which should preserve the // previous parent injector (see https://github.com/angular/angular/issues/1377). // Right now we are destroying any special // context view that might have been used. this._utils.attachViewInContainer(parentView, boundElementIndex, null, null, index, view); this._attachRenderView(parentView, boundElementIndex, index, view); return wtfLeave(s, viewRef); } /** @internal */ _detachViewInContainerScope = wtfCreateScope('AppViewMananger#detachViewInContainer()'); // TODO(i): refactor detachViewInContainer+attachViewInContainer to moveViewInContainer detachViewInContainer(viewContainerLocation: ElementRef, index: number): ViewRef { var s = this._detachViewInContainerScope(); var parentView = internalView((<ElementRef_>viewContainerLocation).parentView); var boundElementIndex = (<ElementRef_>viewContainerLocation).boundElementIndex; var viewContainer = parentView.viewContainers[boundElementIndex]; var view = viewContainer.views[index]; this._utils.detachViewInContainer(parentView, boundElementIndex, index); this._renderer.detachFragment(view.renderFragment); return wtfLeave(s, view.ref); } /** @internal */ _createMainView(protoView: viewModule.AppProtoView, renderViewWithFragments: RenderViewWithFragments): viewModule.AppView { var mergedParentView = this._utils.createView(protoView, renderViewWithFragments, this, this._renderer); this._renderer.setEventDispatcher(mergedParentView.render, mergedParentView); this._viewListener.onViewCreated(mergedParentView); return mergedParentView; } /** @internal */ _createPooledView(protoView: viewModule.AppProtoView): viewModule.AppView { var view = this._viewPool.getView(protoView); if (isBlank(view)) { view = this._createMainView( protoView, this._renderer.createView(protoView.render, protoView.mergeInfo.embeddedViewCount + 1)); } return view; } /** @internal */ _destroyPooledView(view: viewModule.AppView) { var wasReturned = this._viewPool.returnView(view); if (!wasReturned) { this._viewListener.onViewDestroyed(view); this._renderer.destroyView(view.render); } } /** @internal */ _destroyViewInContainer(parentView: viewModule.AppView, boundElementIndex: number, index: number) { var viewContainer = parentView.viewContainers[boundElementIndex]; var view = viewContainer.views[index]; this._viewDehydrateRecurse(view); this._utils.detachViewInContainer(parentView, boundElementIndex, index); if (view.viewOffset > 0) { // Case 1: a view that is part of another view. // Just detach the fragment this._renderer.detachFragment(view.renderFragment); } else { // Case 2: a view that is not part of another view. // dehydrate and destroy it. this._renderer.dehydrateView(view.render); this._renderer.detachFragment(view.renderFragment); this._destroyPooledView(view); } } /** @internal */ _viewDehydrateRecurse(view: viewModule.AppView) { if (view.hydrated()) { this._utils.dehydrateView(view); } var viewContainers = view.viewContainers; var startViewOffset = view.viewOffset; var endViewOffset = view.viewOffset + view.proto.mergeInfo.viewCount - 1; var elementOffset = view.elementOffset; for (var viewIdx = startViewOffset; viewIdx <= endViewOffset; viewIdx++) { var currView = view.views[viewIdx]; for (var binderIdx = 0; binderIdx < currView.proto.elementBinders.length; binderIdx++, elementOffset++) { var vc = viewContainers[elementOffset]; if (isPresent(vc)) { for (var j = vc.views.length - 1; j >= 0; j--) { this._destroyViewInContainer(currView, elementOffset, j); } } } } } }