angular2
Version:
Angular 2 - a web framework for modern web apps
372 lines (333 loc) • 15.2 kB
text/typescript
import {
Injector,
Inject,
Provider,
Injectable,
ResolvedProvider,
forwardRef
} from 'angular2/src/core/di';
import {isPresent, isBlank, isArray} from 'angular2/src/facade/lang';
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {BaseException} from 'angular2/src/facade/exceptions';
import {AppView, HostViewFactory, flattenNestedViewRenderNodes, findLastRenderNode} from './view';
import {AppElement} from './element';
import {ElementRef, ElementRef_} from './element_ref';
import {
HostViewFactoryRef,
HostViewFactoryRef_,
EmbeddedViewRef,
HostViewRef,
ViewRef,
ViewRef_
} from './view_ref';
import {ViewContainerRef} from './view_container_ref';
import {TemplateRef, TemplateRef_} from './template_ref';
import {RootRenderer, RenderComponentType} from 'angular2/src/core/render/api';
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
import {APP_ID} from 'angular2/src/core/application_tokens';
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
import {ViewType} from './view_type';
/**
* 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.
*/
abstract getHostElement(hostViewRef: HostViewRef): ElementRef;
/**
* 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 `hostProtoComponentRef` 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.ProtoComponentRef) => {
* this.viewRef = appViewManager.createRootHostView(protoView, 'some-component', null);
* })
* }
*
* ngOnDestroy() {
* this.appViewManager.destroyRootHostView(this.viewRef);
* this.viewRef = null;
* }
* }
*
* ng.bootstrap(MyApp);
* ```
*/
abstract createRootHostView(hostViewFactoryRef: HostViewFactoryRef, overrideSelector: string,
injector: Injector, projectableNodes?: any[][]): 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): EmbeddedViewRef;
/**
* 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 `dynamicallyCreatedProviders`, 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, hostViewFactoryRef: HostViewFactoryRef,
dynamicallyCreatedProviders: ResolvedProvider[], projectableNodes: any[][]): 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: EmbeddedViewRef): EmbeddedViewRef;
/**
* See {@link AppViewManager#attachViewInContainer}.
*/
abstract detachViewInContainer(viewContainerLocation: ElementRef, index: number): EmbeddedViewRef;
}
@Injectable()
export class AppViewManager_ extends AppViewManager {
private _nextCompTypeId: number = 0;
constructor(private _renderer: RootRenderer, @Inject(APP_ID) private _appId: string) { super(); }
getViewContainer(location: ElementRef): ViewContainerRef {
return (<ElementRef_>location).internalElement.getViewContainerRef();
}
getHostElement(hostViewRef: ViewRef): ElementRef {
var hostView = (<ViewRef_>hostViewRef).internalView;
if (hostView.proto.type !== ViewType.HOST) {
throw new BaseException('This operation is only allowed on host views');
}
return hostView.appElements[0].ref;
}
getNamedElementInComponentView(hostLocation: ElementRef, variableName: string): ElementRef {
var appEl = (<ElementRef_>hostLocation).internalElement;
var componentView = appEl.componentView;
if (isBlank(componentView)) {
throw new BaseException(`There is no component directive at element ${hostLocation}`);
}
for (var i = 0; i < componentView.appElements.length; i++) {
var compAppEl = componentView.appElements[i];
if (StringMapWrapper.contains(compAppEl.proto.directiveVariableBindings, variableName)) {
return compAppEl.ref;
}
}
throw new BaseException(`Could not find variable ${variableName}`);
}
getComponent(hostLocation: ElementRef): any {
return (<ElementRef_>hostLocation).internalElement.getComponent();
}
/** @internal */
_createRootHostViewScope: WtfScopeFn = wtfCreateScope('AppViewManager#createRootHostView()');
createRootHostView(hostViewFactoryRef: HostViewFactoryRef, overrideSelector: string,
injector: Injector, projectableNodes: any[][] = null): HostViewRef {
var s = this._createRootHostViewScope();
var hostViewFactory = (<HostViewFactoryRef_>hostViewFactoryRef).internalHostViewFactory;
var selector = isPresent(overrideSelector) ? overrideSelector : hostViewFactory.selector;
var view = hostViewFactory.viewFactory(this._renderer, this, null, projectableNodes, selector,
null, injector);
return wtfLeave(s, view.ref);
}
/** @internal */
_destroyRootHostViewScope: WtfScopeFn = wtfCreateScope('AppViewManager#destroyRootHostView()');
destroyRootHostView(hostViewRef: ViewRef) {
var s = this._destroyRootHostViewScope();
var hostView = (<ViewRef_>hostViewRef).internalView;
hostView.renderer.detachView(flattenNestedViewRenderNodes(hostView.rootNodesOrAppElements));
hostView.destroy();
wtfLeave(s);
}
/** @internal */
_createEmbeddedViewInContainerScope: WtfScopeFn =
wtfCreateScope('AppViewManager#createEmbeddedViewInContainer()');
createEmbeddedViewInContainer(viewContainerLocation: ElementRef, index: number,
templateRef: TemplateRef): EmbeddedViewRef {
var s = this._createEmbeddedViewInContainerScope();
var contextEl = (<TemplateRef_>templateRef).elementRef.internalElement;
var view: AppView =
contextEl.embeddedViewFactory(contextEl.parentView.renderer, this, contextEl,
contextEl.parentView.projectableNodes, null, null, null);
this._attachViewToContainer(view, (<ElementRef_>viewContainerLocation).internalElement, index);
return wtfLeave(s, view.ref);
}
/** @internal */
_createHostViewInContainerScope: WtfScopeFn =
wtfCreateScope('AppViewManager#createHostViewInContainer()');
createHostViewInContainer(viewContainerLocation: ElementRef, index: number,
hostViewFactoryRef: HostViewFactoryRef,
dynamicallyCreatedProviders: ResolvedProvider[],
projectableNodes: any[][]): HostViewRef {
var s = this._createHostViewInContainerScope();
// TODO(tbosch): This should be specifiable via an additional argument!
var viewContainerLocation_ = <ElementRef_>viewContainerLocation;
var contextEl = viewContainerLocation_.internalElement;
var hostViewFactory = (<HostViewFactoryRef_>hostViewFactoryRef).internalHostViewFactory;
var view = hostViewFactory.viewFactory(
contextEl.parentView.renderer, contextEl.parentView.viewManager, contextEl,
projectableNodes, null, dynamicallyCreatedProviders, null);
this._attachViewToContainer(view, viewContainerLocation_.internalElement, index);
return wtfLeave(s, view.ref);
}
/** @internal */
_destroyViewInContainerScope = wtfCreateScope('AppViewMananger#destroyViewInContainer()');
destroyViewInContainer(viewContainerLocation: ElementRef, index: number) {
var s = this._destroyViewInContainerScope();
var view =
this._detachViewInContainer((<ElementRef_>viewContainerLocation).internalElement, index);
view.destroy();
wtfLeave(s);
}
/** @internal */
_attachViewInContainerScope = wtfCreateScope('AppViewMananger#attachViewInContainer()');
// TODO(i): refactor detachViewInContainer+attachViewInContainer to moveViewInContainer
attachViewInContainer(viewContainerLocation: ElementRef, index: number,
viewRef: ViewRef): EmbeddedViewRef {
var viewRef_ = <ViewRef_>viewRef;
var s = this._attachViewInContainerScope();
this._attachViewToContainer(viewRef_.internalView,
(<ElementRef_>viewContainerLocation).internalElement, index);
return wtfLeave(s, viewRef_);
}
/** @internal */
_detachViewInContainerScope = wtfCreateScope('AppViewMananger#detachViewInContainer()');
// TODO(i): refactor detachViewInContainer+attachViewInContainer to moveViewInContainer
detachViewInContainer(viewContainerLocation: ElementRef, index: number): EmbeddedViewRef {
var s = this._detachViewInContainerScope();
var view =
this._detachViewInContainer((<ElementRef_>viewContainerLocation).internalElement, index);
return wtfLeave(s, view.ref);
}
/** @internal */
onViewCreated(view: AppView) {}
/** @internal */
onViewDestroyed(view: AppView) {}
/** @internal */
createRenderComponentType(encapsulation: ViewEncapsulation,
styles: Array<string | any[]>): RenderComponentType {
return new RenderComponentType(`${this._appId}-${this._nextCompTypeId++}`, encapsulation,
styles);
}
private _attachViewToContainer(view: AppView, vcAppElement: AppElement, viewIndex: number) {
if (view.proto.type === ViewType.COMPONENT) {
throw new BaseException(`Component views can't be moved!`);
}
var nestedViews = vcAppElement.nestedViews;
if (nestedViews == null) {
nestedViews = [];
vcAppElement.nestedViews = nestedViews;
}
ListWrapper.insert(nestedViews, viewIndex, view);
var refNode;
if (viewIndex > 0) {
var prevView = nestedViews[viewIndex - 1];
refNode = prevView.rootNodesOrAppElements.length > 0 ?
prevView.rootNodesOrAppElements[prevView.rootNodesOrAppElements.length - 1] :
null;
} else {
refNode = vcAppElement.nativeElement;
}
if (isPresent(refNode)) {
var refRenderNode = findLastRenderNode(refNode);
view.renderer.attachViewAfter(refRenderNode,
flattenNestedViewRenderNodes(view.rootNodesOrAppElements));
}
// TODO: This is only needed when a view is destroyed,
// not when it is detached for reordering with ng-for...
vcAppElement.parentView.changeDetector.addContentChild(view.changeDetector);
vcAppElement.traverseAndSetQueriesAsDirty();
}
private _detachViewInContainer(vcAppElement: AppElement, viewIndex: number): AppView {
var view = ListWrapper.removeAt(vcAppElement.nestedViews, viewIndex);
if (view.proto.type === ViewType.COMPONENT) {
throw new BaseException(`Component views can't be moved!`);
}
vcAppElement.traverseAndSetQueriesAsDirty();
view.renderer.detachView(flattenNestedViewRenderNodes(view.rootNodesOrAppElements));
// TODO: This is only needed when a view is destroyed,
// not when it is detached for reordering with ng-for...
view.changeDetector.remove();
return view;
}
}