464 lines
14 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ApplicationRef} from '../application_ref';
import {ChangeDetectorRef, ChangeDetectorStatus} from '../change_detection/change_detection';
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
2016-07-13 11:01:32 -07:00
import {isPresent} from '../facade/lang';
import {WtfScopeFn, wtfCreateScope, wtfLeave} from '../profile/profile';
import {DirectRenderer, RenderComponentType, RenderDebugInfo, Renderer} from '../render/api';
import {AnimationViewContext} from './animation_view_context';
import {ComponentRef} from './component_factory';
import {DebugContext, StaticNodeDebugInfo} from './debug_context';
import {ElementInjector} from './element_injector';
import {ExpressionChangedAfterItHasBeenCheckedError, ViewDestroyedError, ViewWrappedError} from './errors';
import {ViewContainer} from './view_container';
import {ViewRef_} from './view_ref';
import {ViewType} from './view_type';
import {ViewUtils, addToArray} from './view_utils';
const _scope_check: WtfScopeFn = wtfCreateScope(`AppView#check(ascii id)`);
/**
* @experimental
*/
const EMPTY_CONTEXT = new Object();
const UNDEFINED = new Object();
/**
* Cost of making objects: http://jsperf.com/instantiate-size-of-object
*
2014-10-10 20:44:55 -07:00
*/
export abstract class AppView<T> {
ref: ViewRef_<T>;
lastRootNode: any;
allNodes: any[];
disposables: Function[];
viewContainer: ViewContainer;
// This will be set if a view is directly attached to an ApplicationRef
// and not to a view container.
appRef: ApplicationRef;
numberOfChecks: number = 0;
feat(compiler): attach components and project light dom during compilation. Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
2015-06-24 13:46:39 -07:00
renderer: Renderer;
private _hasExternalHostElement: boolean;
private _hostInjector: Injector;
private _hostProjectableNodes: any[][];
private _animationContext: AnimationViewContext;
private _directRenderer: DirectRenderer;
public context: T;
constructor(
public clazz: any, public componentType: RenderComponentType, public type: ViewType,
public viewUtils: ViewUtils, public parentView: AppView<any>, public parentIndex: number,
public parentElement: any, public cdMode: ChangeDetectorStatus,
public declaredViewContainer: ViewContainer = null) {
this.ref = new ViewRef_(this);
if (type === ViewType.COMPONENT || type === ViewType.HOST) {
this.renderer = viewUtils.renderComponent(componentType);
} else {
this.renderer = parentView.renderer;
}
this._directRenderer = (this.renderer as any).directRenderer;
}
get animationContext(): AnimationViewContext {
if (!this._animationContext) {
this._animationContext = new AnimationViewContext();
}
return this._animationContext;
}
get destroyed(): boolean { return this.cdMode === ChangeDetectorStatus.Destroyed; }
create(context: T) {
this.context = context;
return this.createInternal(null);
}
createHostView(rootSelectorOrNode: string|any, hostInjector: Injector, projectableNodes: any[][]):
ComponentRef<any> {
this.context = <any>EMPTY_CONTEXT;
this._hasExternalHostElement = isPresent(rootSelectorOrNode);
this._hostInjector = hostInjector;
this._hostProjectableNodes = projectableNodes;
return this.createInternal(rootSelectorOrNode);
}
feat(compiler): attach components and project light dom during compilation. Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
2015-06-24 13:46:39 -07:00
/**
* Overwritten by implementations.
* Returns the ComponentRef for the host element for ViewType.HOST.
*/
createInternal(rootSelectorOrNode: string|any): ComponentRef<any> { return null; }
/**
* Overwritten by implementations.
*/
createEmbeddedViewInternal(templateNodeIndex: number): AppView<any> { return null; }
init(lastRootNode: any, allNodes: any[], disposables: Function[]) {
this.lastRootNode = lastRootNode;
this.allNodes = allNodes;
this.disposables = disposables;
if (this.type === ViewType.COMPONENT) {
this.dirtyParentQueriesInternal();
}
}
injectorGet(token: any, nodeIndex: number, notFoundValue: any = THROW_IF_NOT_FOUND): any {
let result = UNDEFINED;
let view: AppView<any> = this;
while (result === UNDEFINED) {
if (isPresent(nodeIndex)) {
result = view.injectorGetInternal(token, nodeIndex, UNDEFINED);
}
if (result === UNDEFINED && view.type === ViewType.HOST) {
result = view._hostInjector.get(token, notFoundValue);
}
nodeIndex = view.parentIndex;
view = view.parentView;
}
return result;
}
/**
* Overwritten by implementations
*/
injectorGetInternal(token: any, nodeIndex: number, notFoundResult: any): any {
return notFoundResult;
}
injector(nodeIndex: number): Injector { return new ElementInjector(this, nodeIndex); }
detachAndDestroy() {
if (this.viewContainer) {
this.viewContainer.detachView(this.viewContainer.nestedViews.indexOf(this));
} else if (this.appRef) {
this.appRef.detachView(this.ref);
} else if (this._hasExternalHostElement) {
this.detach();
}
this.destroy();
}
destroy() {
if (this.cdMode === ChangeDetectorStatus.Destroyed) {
return;
}
const hostElement = this.type === ViewType.COMPONENT ? this.parentElement : null;
if (this.disposables) {
for (let i = 0; i < this.disposables.length; i++) {
this.disposables[i]();
}
}
this.destroyInternal();
this.dirtyParentQueriesInternal();
if (this._animationContext) {
this._animationContext.onAllActiveAnimationsDone(
() => this.renderer.destroyView(hostElement, this.allNodes));
} else {
this.renderer.destroyView(hostElement, this.allNodes);
}
this.cdMode = ChangeDetectorStatus.Destroyed;
}
/**
* Overwritten by implementations
*/
destroyInternal(): void {}
/**
* Overwritten by implementations
*/
detachInternal(): void {}
detach(): void {
this.detachInternal();
if (this._animationContext) {
this._animationContext.onAllActiveAnimationsDone(() => this._renderDetach());
} else {
this._renderDetach();
}
if (this.declaredViewContainer && this.declaredViewContainer !== this.viewContainer &&
this.declaredViewContainer.projectedViews) {
const projectedViews = this.declaredViewContainer.projectedViews;
const index = projectedViews.indexOf(this);
// perf: pop is faster than splice!
if (index >= projectedViews.length - 1) {
projectedViews.pop();
} else {
projectedViews.splice(index, 1);
}
}
this.appRef = null;
this.viewContainer = null;
this.dirtyParentQueriesInternal();
}
private _renderDetach() {
if (this._directRenderer) {
this.visitRootNodesInternal(this._directRenderer.remove, null);
} else {
this.renderer.detachView(this.flatRootNodes);
}
}
attachToAppRef(appRef: ApplicationRef) {
if (this.viewContainer) {
throw new Error('This view is already attached to a ViewContainer!');
}
this.appRef = appRef;
this.dirtyParentQueriesInternal();
}
attachAfter(viewContainer: ViewContainer, prevView: AppView<any>) {
if (this.appRef) {
throw new Error('This view is already attached directly to the ApplicationRef!');
}
this._renderAttach(viewContainer, prevView);
this.viewContainer = viewContainer;
if (this.declaredViewContainer && this.declaredViewContainer !== viewContainer) {
if (!this.declaredViewContainer.projectedViews) {
this.declaredViewContainer.projectedViews = [];
}
this.declaredViewContainer.projectedViews.push(this);
}
this.dirtyParentQueriesInternal();
}
moveAfter(viewContainer: ViewContainer, prevView: AppView<any>) {
this._renderAttach(viewContainer, prevView);
this.dirtyParentQueriesInternal();
}
private _renderAttach(viewContainer: ViewContainer, prevView: AppView<any>) {
const prevNode = prevView ? prevView.lastRootNode : viewContainer.nativeElement;
if (this._directRenderer) {
const nextSibling = this._directRenderer.nextSibling(prevNode);
if (nextSibling) {
this.visitRootNodesInternal(this._directRenderer.insertBefore, nextSibling);
} else {
const parentElement = this._directRenderer.parentElement(prevNode);
if (parentElement) {
this.visitRootNodesInternal(this._directRenderer.appendChild, parentElement);
}
}
} else {
this.renderer.attachViewAfter(prevNode, this.flatRootNodes);
}
}
get changeDetectorRef(): ChangeDetectorRef { return this.ref; }
get flatRootNodes(): any[] {
const nodes: any[] = [];
this.visitRootNodesInternal(addToArray, nodes);
return nodes;
}
projectNodes(parentElement: any, ngContentIndex: number) {
if (this._directRenderer) {
this.visitProjectedNodes(ngContentIndex, this._directRenderer.appendChild, parentElement);
} else {
const nodes: any[] = [];
this.visitProjectedNodes(ngContentIndex, addToArray, nodes);
this.renderer.projectNodes(parentElement, nodes);
}
}
visitProjectedNodes<C>(ngContentIndex: number, cb: (node: any, ctx: C) => void, c: C): void {
switch (this.type) {
case ViewType.EMBEDDED:
this.parentView.visitProjectedNodes(ngContentIndex, cb, c);
break;
case ViewType.COMPONENT:
if (this.parentView.type === ViewType.HOST) {
const nodes = this.parentView._hostProjectableNodes[ngContentIndex] || [];
for (let i = 0; i < nodes.length; i++) {
cb(nodes[i], c);
}
} else {
this.parentView.visitProjectableNodesInternal(this.parentIndex, ngContentIndex, cb, c);
}
break;
}
}
/**
* Overwritten by implementations
*/
visitRootNodesInternal<C>(cb: (node: any, ctx: C) => void, c: C): void {}
/**
* Overwritten by implementations
*/
visitProjectableNodesInternal<C>(
nodeIndex: number, ngContentIndex: number, cb: (node: any, ctx: C) => void, c: C): void {}
/**
* Overwritten by implementations
*/
dirtyParentQueriesInternal(): void {}
detectChanges(throwOnChange: boolean): void {
const s = _scope_check(this.clazz);
if (this.cdMode === ChangeDetectorStatus.Checked ||
this.cdMode === ChangeDetectorStatus.Errored ||
this.cdMode === ChangeDetectorStatus.Detached)
return;
if (this.cdMode === ChangeDetectorStatus.Destroyed) {
this.throwDestroyedError('detectChanges');
}
this.detectChangesInternal(throwOnChange);
if (this.cdMode === ChangeDetectorStatus.CheckOnce) this.cdMode = ChangeDetectorStatus.Checked;
this.numberOfChecks++;
wtfLeave(s);
}
/**
* Overwritten by implementations
*/
detectChangesInternal(throwOnChange: boolean): void {}
2015-06-12 09:45:31 -07:00
markAsCheckOnce(): void { this.cdMode = ChangeDetectorStatus.CheckOnce; }
feat(compiler): attach components and project light dom during compilation. Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
2015-06-24 13:46:39 -07:00
markPathToRootAsCheckOnce(): void {
let c: AppView<any> = this;
while (isPresent(c) && c.cdMode !== ChangeDetectorStatus.Detached) {
if (c.cdMode === ChangeDetectorStatus.Checked) {
c.cdMode = ChangeDetectorStatus.CheckOnce;
}
if (c.type === ViewType.COMPONENT) {
c = c.parentView;
} else {
c = c.viewContainer ? c.viewContainer.parentView : null;
}
}
}
2014-09-28 16:29:11 -07:00
eventHandler<E, R>(cb: (eventName: string, event?: E) => R): (eventName: string, event?: E) => R {
return cb;
}
throwDestroyedError(details: string): void { throw new ViewDestroyedError(details); }
}
export class DebugAppView<T> extends AppView<T> {
private _currentDebugContext: DebugContext = null;
constructor(
clazz: any, componentType: RenderComponentType, type: ViewType, viewUtils: ViewUtils,
parentView: AppView<any>, parentIndex: number, parentNode: any, cdMode: ChangeDetectorStatus,
public staticNodeDebugInfos: StaticNodeDebugInfo[],
declaredViewContainer: ViewContainer = null) {
super(
clazz, componentType, type, viewUtils, parentView, parentIndex, parentNode, cdMode,
declaredViewContainer);
}
create(context: T) {
this._resetDebug();
try {
return super.create(context);
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
}
createHostView(
rootSelectorOrNode: string|any, injector: Injector,
projectableNodes: any[][] = null): ComponentRef<any> {
this._resetDebug();
try {
return super.createHostView(rootSelectorOrNode, injector, projectableNodes);
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
}
2016-11-02 07:36:31 -07:00
injectorGet(token: any, nodeIndex: number, notFoundResult?: any): any {
this._resetDebug();
try {
return super.injectorGet(token, nodeIndex, notFoundResult);
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
}
detach(): void {
this._resetDebug();
try {
super.detach();
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
}
destroy() {
this._resetDebug();
try {
super.destroy();
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
}
detectChanges(throwOnChange: boolean): void {
this._resetDebug();
try {
super.detectChanges(throwOnChange);
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
}
private _resetDebug() { this._currentDebugContext = null; }
debug(nodeIndex: number, rowNum: number, colNum: number): DebugContext {
return this._currentDebugContext = new DebugContext(this, nodeIndex, rowNum, colNum);
}
private _rethrowWithContext(e: any) {
if (!(e instanceof ViewWrappedError)) {
if (!(e instanceof ExpressionChangedAfterItHasBeenCheckedError)) {
this.cdMode = ChangeDetectorStatus.Errored;
}
if (isPresent(this._currentDebugContext)) {
throw new ViewWrappedError(e, this._currentDebugContext);
}
}
}
eventHandler<E, R>(cb: (eventName: string, event?: E) => R): (eventName: string, event?: E) => R {
const superHandler = super.eventHandler(cb);
return (eventName: string, event?: any) => {
this._resetDebug();
try {
return superHandler.call(this, eventName, event);
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
};
}
}