421 lines
12 KiB
TypeScript
Raw Normal View History

import {
ListWrapper,
MapWrapper,
Map,
StringMapWrapper,
isListLikeIterable,
areIterablesEqual
} from 'angular2/src/facade/collection';
2015-05-20 09:48:15 -07:00
import {Injector} from 'angular2/src/core/di';
import {AppElement} from './element';
import {
assertionsEnabled,
isPresent,
isBlank,
Type,
isArray,
isNumber,
CONST,
CONST_EXPR,
stringify,
isPrimitive
} from 'angular2/src/facade/lang';
import {ObservableWrapper} from 'angular2/src/facade/async';
import {Renderer, RootRenderer, RenderComponentType} from 'angular2/src/core/render/api';
import {ViewRef_, HostViewFactoryRef} from './view_ref';
import {AppViewManager_, AppViewManager} from './view_manager';
import {ViewType} from './view_type';
import {
flattenNestedViewRenderNodes,
ensureSlotCount,
arrayLooseIdentical,
mapLooseIdentical
} from './view_utils';
import {
ChangeDetectorRef,
ChangeDetectionStrategy,
ChangeDetectorState,
isDefaultChangeDetectionStrategy,
devModeEqual
} from 'angular2/src/core/change_detection/change_detection';
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
import {
ExpressionChangedAfterItHasBeenCheckedException,
ViewDestroyedException,
ViewWrappedException
} from './exceptions';
import {StaticNodeDebugInfo, DebugContext} from './debug_context';
import {ElementInjector} from './element_injector';
export const HOST_VIEW_ELEMENT_NAME = '$hostViewEl';
const EMPTY_CONTEXT = CONST_EXPR(new Object());
2014-09-28 16:29:11 -07:00
var _scope_check: WtfScopeFn = wtfCreateScope(`AppView#check(ascii id)`);
/**
* 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_;
rootNodesOrAppElements: any[];
allNodes: any[];
disposables: Function[];
subscriptions: any[];
namedAppElements: {[key: string]: AppElement};
contentChildren: AppView<any>[] = [];
viewChildren: AppView<any>[] = [];
renderParent: AppView<any>;
private _literalArrayCache: any[][];
private _literalMapCache: Array<{[key: string]: any}>;
// The names of the below fields must be kept in sync with codegen_name_util.ts or
// change detection will fail.
cdState: ChangeDetectorState = ChangeDetectorState.NeverChecked;
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
/**
* The context against which data-binding expressions in this view are evaluated against.
* This is always a component instance.
*/
context: T = null;
projectableNodes: Array<any | any[]>;
destroyed: boolean = false;
renderer: Renderer;
private _currentDebugContext: DebugContext = null;
constructor(public clazz: any, public componentType: RenderComponentType, public type: ViewType,
public locals: {[key: string]: any}, public viewManager: AppViewManager_,
public parentInjector: Injector, public declarationAppElement: AppElement,
public cdMode: ChangeDetectionStrategy, literalArrayCacheSize: number,
literalMapCacheSize: number, public staticNodeDebugInfos: StaticNodeDebugInfo[]) {
this.ref = new ViewRef_(this);
if (type === ViewType.COMPONENT || type === ViewType.HOST) {
this.renderer = viewManager.renderComponent(componentType);
} else {
this.renderer = declarationAppElement.parentView.renderer;
}
this._literalArrayCache = ListWrapper.createFixedSize(literalArrayCacheSize);
this._literalMapCache = ListWrapper.createFixedSize(literalMapCacheSize);
}
create(givenProjectableNodes: Array<any | any[]>, rootSelector: string) {
var context;
var projectableNodes;
switch (this.type) {
case ViewType.COMPONENT:
context = this.declarationAppElement.component;
projectableNodes = ensureSlotCount(givenProjectableNodes, this.componentType.slotCount);
break;
case ViewType.EMBEDDED:
context = this.declarationAppElement.parentView.context;
projectableNodes = this.declarationAppElement.parentView.projectableNodes;
break;
case ViewType.HOST:
context = EMPTY_CONTEXT;
// Note: Don't ensure the slot count for the projectableNodes as we store
// them only for the contained component view (which will later check the slot count...)
projectableNodes = givenProjectableNodes;
break;
}
this.context = context;
this.projectableNodes = projectableNodes;
if (this.debugMode) {
this._resetDebug();
try {
this.createInternal(rootSelector);
} catch (e) {
this._rethrowWithContext(e, e.stack);
throw e;
}
} else {
this.createInternal(rootSelector);
}
}
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
*/
createInternal(rootSelector: string): void {}
init(rootNodesOrAppElements: any[], allNodes: any[], appElements: {[key: string]: AppElement},
disposables: Function[], subscriptions: any[]) {
this.rootNodesOrAppElements = rootNodesOrAppElements;
this.allNodes = allNodes;
this.namedAppElements = appElements;
this.disposables = disposables;
this.subscriptions = subscriptions;
if (this.type === ViewType.COMPONENT) {
// Note: the render nodes have been attached to their host element
// in the ViewFactory already.
this.declarationAppElement.parentView.viewChildren.push(this);
this.renderParent = this.declarationAppElement.parentView;
this.dirtyParentQueriesInternal();
}
}
getHostViewElement(): AppElement { return this.namedAppElements[HOST_VIEW_ELEMENT_NAME]; }
injectorGet(token: any, nodeIndex: number, notFoundResult: any): any {
if (this.debugMode) {
this._resetDebug();
try {
return this.injectorGetInternal(token, nodeIndex, notFoundResult);
} catch (e) {
this._rethrowWithContext(e, e.stack);
throw e;
}
} else {
return this.injectorGetInternal(token, nodeIndex, notFoundResult);
}
}
/**
* Overwritten by implementations
*/
injectorGetInternal(token: any, nodeIndex: number, notFoundResult: any): any {
return notFoundResult;
}
injector(nodeIndex: number): Injector {
if (isPresent(nodeIndex)) {
return new ElementInjector(this, nodeIndex);
} else {
return this.parentInjector;
}
}
destroy() {
if (this.destroyed) {
return;
}
var children = this.contentChildren;
for (var i = 0; i < children.length; i++) {
children[i].destroy();
}
children = this.viewChildren;
for (var i = 0; i < children.length; i++) {
children[i].destroy();
}
if (this.debugMode) {
this._resetDebug();
try {
this._destroyLocal();
} catch (e) {
this._rethrowWithContext(e, e.stack);
throw e;
}
} else {
this._destroyLocal();
}
this.destroyed = true;
}
private _destroyLocal() {
var hostElement =
this.type === ViewType.COMPONENT ? this.declarationAppElement.nativeElement : null;
this.renderer.destroyView(hostElement, this.allNodes);
for (var i = 0; i < this.disposables.length; i++) {
this.disposables[i]();
}
for (var i = 0; i < this.subscriptions.length; i++) {
ObservableWrapper.dispose(this.subscriptions[i]);
}
this.destroyInternal();
this.dirtyParentQueriesInternal();
}
/**
* Overwritten by implementations
*/
destroyInternal(): void {}
get debugMode(): boolean { return isPresent(this.staticNodeDebugInfos); }
get changeDetectorRef(): ChangeDetectorRef { return this.ref; }
get flatRootNodes(): any[] { return flattenNestedViewRenderNodes(this.rootNodesOrAppElements); }
get lastRootNode(): any {
var lastNode = this.rootNodesOrAppElements.length > 0 ?
this.rootNodesOrAppElements[this.rootNodesOrAppElements.length - 1] :
null;
return _findLastRenderNode(lastNode);
}
hasLocal(contextName: string): boolean {
return StringMapWrapper.contains(this.locals, contextName);
}
setLocal(contextName: string, value: any): void { this.locals[contextName] = value; }
/**
* Overwritten by implementations
*/
dirtyParentQueriesInternal(): void {}
addRenderContentChild(view: AppView<any>): void {
this.contentChildren.push(view);
view.renderParent = this;
view.dirtyParentQueriesInternal();
}
removeContentChild(view: AppView<any>): void {
ListWrapper.remove(this.contentChildren, view);
view.dirtyParentQueriesInternal();
view.renderParent = null;
}
detectChanges(throwOnChange: boolean): void {
var s = _scope_check(this.clazz);
if (this.cdMode === ChangeDetectionStrategy.Detached ||
this.cdMode === ChangeDetectionStrategy.Checked ||
this.cdState === ChangeDetectorState.Errored)
return;
if (this.destroyed) {
this.throwDestroyedError('detectChanges');
}
if (this.debugMode) {
this._resetDebug();
try {
this.detectChangesInternal(throwOnChange);
} catch (e) {
this._rethrowWithContext(e, e.stack);
throw e;
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
}
} else {
this.detectChangesInternal(throwOnChange);
}
if (this.cdMode === ChangeDetectionStrategy.CheckOnce)
this.cdMode = ChangeDetectionStrategy.Checked;
this.cdState = ChangeDetectorState.CheckedBefore;
wtfLeave(s);
}
/**
* Overwritten by implementations
*/
detectChangesInternal(throwOnChange: boolean): void {
this.detectContentChildrenChanges(throwOnChange);
this.detectViewChildrenChanges(throwOnChange);
}
detectContentChildrenChanges(throwOnChange: boolean) {
for (var i = 0; i < this.contentChildren.length; ++i) {
this.contentChildren[i].detectChanges(throwOnChange);
2015-06-12 09:45:31 -07:00
}
}
detectViewChildrenChanges(throwOnChange: boolean) {
for (var i = 0; i < this.viewChildren.length; ++i) {
this.viewChildren[i].detectChanges(throwOnChange);
}
}
literalArray(id: number, value: any[]): any[] {
var prevValue = this._literalArrayCache[id];
if (isBlank(value)) {
return value;
}
if (isBlank(prevValue) || !arrayLooseIdentical(prevValue, value)) {
prevValue = this._literalArrayCache[id] = value;
}
return prevValue;
}
literalMap(id: number, value: {[key: string]: any}): {[key: string]: any} {
var prevValue = this._literalMapCache[id];
if (isBlank(value)) {
return value;
}
if (isBlank(prevValue) || !mapLooseIdentical(prevValue, value)) {
prevValue = this._literalMapCache[id] = value;
}
return prevValue;
}
markAsCheckOnce(): void { this.cdMode = ChangeDetectionStrategy.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 {
var c: AppView<any> = this;
while (isPresent(c) && c.cdMode !== ChangeDetectionStrategy.Detached) {
if (c.cdMode === ChangeDetectionStrategy.Checked) {
c.cdMode = ChangeDetectionStrategy.CheckOnce;
}
c = c.renderParent;
}
}
2014-09-28 16:29:11 -07:00
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, stack: any) {
if (!(e instanceof ViewWrappedException)) {
if (!(e instanceof ExpressionChangedAfterItHasBeenCheckedException)) {
this.cdState = ChangeDetectorState.Errored;
}
if (isPresent(this._currentDebugContext)) {
throw new ViewWrappedException(e, stack, this._currentDebugContext);
}
}
}
eventHandler(cb: Function): Function {
if (this.debugMode) {
return (event) => {
this._resetDebug();
try {
return cb(event);
} catch (e) {
this._rethrowWithContext(e, e.stack);
throw e;
}
};
} else {
return cb;
}
}
throwDestroyedError(details: string): void { throw new ViewDestroyedException(details); }
}
@CONST()
export class HostViewFactory {
constructor(public selector: string, public viewFactory: Function) {}
}
function _findLastRenderNode(node: any): any {
var lastNode;
if (node instanceof AppElement) {
var appEl = <AppElement>node;
lastNode = appEl.nativeElement;
if (isPresent(appEl.nestedViews)) {
// Note: Views might have no root nodes at all!
for (var i = appEl.nestedViews.length - 1; i >= 0; i--) {
var nestedView = appEl.nestedViews[i];
if (nestedView.rootNodesOrAppElements.length > 0) {
lastNode = _findLastRenderNode(
nestedView.rootNodesOrAppElements[nestedView.rootNodesOrAppElements.length - 1]);
}
}
}
} else {
lastNode = node;
}
return lastNode;
}