2016-04-12 09:40:37 -07:00
|
|
|
import {
|
|
|
|
ListWrapper,
|
|
|
|
MapWrapper,
|
|
|
|
Map,
|
|
|
|
StringMapWrapper,
|
2016-01-06 14:13:44 -08:00
|
|
|
isListLikeIterable,
|
|
|
|
areIterablesEqual
|
2016-04-12 09:40:37 -07:00
|
|
|
} from 'angular2/src/facade/collection';
|
2015-05-20 09:48:15 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
import {Injector} from 'angular2/src/core/di';
|
|
|
|
import {AppElement} from './element';
|
2016-04-12 09:40:37 -07:00
|
|
|
import {
|
2016-01-06 14:13:44 -08:00
|
|
|
assertionsEnabled,
|
2016-04-12 09:40:37 -07:00
|
|
|
isPresent,
|
|
|
|
isBlank,
|
|
|
|
Type,
|
|
|
|
isArray,
|
|
|
|
isNumber,
|
|
|
|
CONST,
|
2016-01-06 14:13:44 -08:00
|
|
|
CONST_EXPR,
|
|
|
|
stringify,
|
|
|
|
isPrimitive
|
2016-04-12 09:40:37 -07:00
|
|
|
} from 'angular2/src/facade/lang';
|
2016-01-06 14:13:44 -08:00
|
|
|
|
|
|
|
import {ObservableWrapper} from 'angular2/src/facade/async';
|
|
|
|
import {Renderer, RootRenderer, RenderComponentType} from 'angular2/src/core/render/api';
|
2015-12-02 10:35:51 -08:00
|
|
|
import {ViewRef_, HostViewFactoryRef} from './view_ref';
|
2015-04-24 17:53:06 -07:00
|
|
|
|
2015-12-02 10:35:51 -08:00
|
|
|
import {AppViewManager_, AppViewManager} from './view_manager';
|
|
|
|
import {ViewType} from './view_type';
|
2016-01-06 14:13:44 -08:00
|
|
|
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';
|
2015-07-28 12:43:41 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
export const HOST_VIEW_ELEMENT_NAME = '$hostViewEl';
|
2015-08-20 15:11:12 -07:00
|
|
|
|
2015-12-02 10:35:51 -08:00
|
|
|
const EMPTY_CONTEXT = CONST_EXPR(new Object());
|
2014-09-28 16:29:11 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
var _scope_check: WtfScopeFn = wtfCreateScope(`AppView#check(ascii id)`);
|
|
|
|
|
2014-11-21 15:13:01 -08:00
|
|
|
/**
|
2015-07-07 08:15:58 +02:00
|
|
|
* Cost of making objects: http://jsperf.com/instantiate-size-of-object
|
2015-03-31 22:47:11 +00:00
|
|
|
*
|
2014-10-10 20:44:55 -07:00
|
|
|
*/
|
2016-01-06 14:13:44 -08:00
|
|
|
export abstract class AppView<T> {
|
2015-12-02 10:35:51 -08:00
|
|
|
ref: ViewRef_;
|
|
|
|
rootNodesOrAppElements: any[];
|
|
|
|
allNodes: any[];
|
|
|
|
disposables: Function[];
|
2016-01-06 14:13:44 -08:00
|
|
|
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;
|
2015-06-24 13:46:39 -07:00
|
|
|
|
2015-04-06 13:19:30 -07:00
|
|
|
/**
|
|
|
|
* The context against which data-binding expressions in this view are evaluated against.
|
|
|
|
* This is always a component instance.
|
|
|
|
*/
|
2016-01-06 14:13:44 -08:00
|
|
|
context: T = null;
|
2015-04-06 13:19:30 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
projectableNodes: Array<any | any[]>;
|
2015-01-02 14:23:59 -08:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
destroyed: boolean = false;
|
2015-12-02 10:35:51 -08:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
renderer: Renderer;
|
2015-12-02 10:35:51 -08:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
private _currentDebugContext: DebugContext = null;
|
2015-12-02 10:35:51 -08:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
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[]) {
|
2015-10-06 06:53:39 -07:00
|
|
|
this.ref = new ViewRef_(this);
|
2016-01-06 14:13:44 -08:00
|
|
|
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) {
|
2015-12-02 10:35:51 -08:00
|
|
|
var context;
|
2016-01-06 14:13:44 -08:00
|
|
|
var projectableNodes;
|
|
|
|
switch (this.type) {
|
2015-12-02 10:35:51 -08:00
|
|
|
case ViewType.COMPONENT:
|
2016-01-06 14:13:44 -08:00
|
|
|
context = this.declarationAppElement.component;
|
|
|
|
projectableNodes = ensureSlotCount(givenProjectableNodes, this.componentType.slotCount);
|
2015-12-02 10:35:51 -08:00
|
|
|
break;
|
|
|
|
case ViewType.EMBEDDED:
|
2016-01-06 14:13:44 -08:00
|
|
|
context = this.declarationAppElement.parentView.context;
|
|
|
|
projectableNodes = this.declarationAppElement.parentView.projectableNodes;
|
2015-12-02 10:35:51 -08:00
|
|
|
break;
|
|
|
|
case ViewType.HOST:
|
|
|
|
context = EMPTY_CONTEXT;
|
2016-01-06 14:13:44 -08:00
|
|
|
// 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;
|
2015-12-02 10:35:51 -08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
this.context = context;
|
2016-01-06 14:13:44 -08:00
|
|
|
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);
|
|
|
|
}
|
2015-12-02 10:35:51 -08:00
|
|
|
}
|
2015-06-24 13:46:39 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
/**
|
|
|
|
* Overwritten by implementations
|
|
|
|
*/
|
|
|
|
createInternal(rootSelector: string): void {}
|
|
|
|
|
|
|
|
init(rootNodesOrAppElements: any[], allNodes: any[], appElements: {[key: string]: AppElement},
|
|
|
|
disposables: Function[], subscriptions: any[]) {
|
2015-12-02 10:35:51 -08:00
|
|
|
this.rootNodesOrAppElements = rootNodesOrAppElements;
|
|
|
|
this.allNodes = allNodes;
|
2016-01-06 14:13:44 -08:00
|
|
|
this.namedAppElements = appElements;
|
2015-12-02 10:35:51 -08:00
|
|
|
this.disposables = disposables;
|
2016-01-06 14:13:44 -08:00
|
|
|
this.subscriptions = subscriptions;
|
|
|
|
if (this.type === ViewType.COMPONENT) {
|
2015-12-02 10:35:51 -08:00
|
|
|
// Note: the render nodes have been attached to their host element
|
|
|
|
// in the ViewFactory already.
|
2016-01-06 14:13:44 -08:00
|
|
|
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;
|
2015-12-02 10:35:51 -08:00
|
|
|
}
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
|
|
|
|
2015-12-02 10:35:51 -08:00
|
|
|
destroy() {
|
|
|
|
if (this.destroyed) {
|
2016-01-06 14:13:44 -08:00
|
|
|
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();
|
2015-12-02 10:35:51 -08:00
|
|
|
}
|
2014-12-01 18:41:55 -08:00
|
|
|
|
2015-12-02 10:35:51 -08:00
|
|
|
this.destroyed = true;
|
2016-01-06 14:13:44 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
private _destroyLocal() {
|
2015-12-02 10:35:51 -08:00
|
|
|
var hostElement =
|
2016-01-06 14:13:44 -08:00
|
|
|
this.type === ViewType.COMPONENT ? this.declarationAppElement.nativeElement : null;
|
2015-12-02 10:35:51 -08:00
|
|
|
this.renderer.destroyView(hostElement, this.allNodes);
|
|
|
|
for (var i = 0; i < this.disposables.length; i++) {
|
|
|
|
this.disposables[i]();
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
2016-01-06 14:13:44 -08:00
|
|
|
for (var i = 0; i < this.subscriptions.length; i++) {
|
|
|
|
ObservableWrapper.dispose(this.subscriptions[i]);
|
|
|
|
}
|
|
|
|
this.destroyInternal();
|
|
|
|
|
|
|
|
this.dirtyParentQueriesInternal();
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
/**
|
|
|
|
* Overwritten by implementations
|
|
|
|
*/
|
|
|
|
destroyInternal(): void {}
|
|
|
|
|
|
|
|
get debugMode(): boolean { return isPresent(this.staticNodeDebugInfos); }
|
|
|
|
|
|
|
|
get changeDetectorRef(): ChangeDetectorRef { return this.ref; }
|
2014-12-01 18:41:55 -08:00
|
|
|
|
2015-12-02 10:35:51 -08:00
|
|
|
get flatRootNodes(): any[] { return flattenNestedViewRenderNodes(this.rootNodesOrAppElements); }
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
get lastRootNode(): any {
|
|
|
|
var lastNode = this.rootNodesOrAppElements.length > 0 ?
|
|
|
|
this.rootNodesOrAppElements[this.rootNodesOrAppElements.length - 1] :
|
|
|
|
null;
|
|
|
|
return _findLastRenderNode(lastNode);
|
|
|
|
}
|
|
|
|
|
2015-12-02 10:35:51 -08:00
|
|
|
hasLocal(contextName: string): boolean {
|
2016-01-06 14:13:44 -08:00
|
|
|
return StringMapWrapper.contains(this.locals, contextName);
|
2015-12-02 10:35:51 -08:00
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
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();
|
2015-03-10 10:03:26 +01:00
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
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;
|
2015-06-24 13:46:39 -07:00
|
|
|
}
|
2016-01-06 14:13:44 -08:00
|
|
|
} else {
|
|
|
|
this.detectChangesInternal(throwOnChange);
|
2015-04-07 20:54:20 -07:00
|
|
|
}
|
2016-01-06 14:13:44 -08:00
|
|
|
if (this.cdMode === ChangeDetectionStrategy.CheckOnce)
|
|
|
|
this.cdMode = ChangeDetectionStrategy.Checked;
|
|
|
|
|
|
|
|
this.cdState = ChangeDetectorState.CheckedBefore;
|
|
|
|
wtfLeave(s);
|
2015-04-07 20:54:20 -07:00
|
|
|
}
|
2015-04-09 21:20:11 +02:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
/**
|
|
|
|
* Overwritten by implementations
|
|
|
|
*/
|
|
|
|
detectChangesInternal(throwOnChange: boolean): void {
|
|
|
|
this.detectContentChildrenChanges(throwOnChange);
|
|
|
|
this.detectViewChildrenChanges(throwOnChange);
|
2015-08-20 15:11:12 -07:00
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
detectViewChildrenChanges(throwOnChange: boolean) {
|
|
|
|
for (var i = 0; i < this.viewChildren.length; ++i) {
|
|
|
|
this.viewChildren[i].detectChanges(throwOnChange);
|
2015-08-18 21:51:28 -07:00
|
|
|
}
|
2015-08-28 18:11:04 -07:00
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
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;
|
2015-07-23 18:01:34 -07:00
|
|
|
}
|
2016-01-06 14:13:44 -08:00
|
|
|
return prevValue;
|
2015-07-22 12:00:35 -07:00
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
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;
|
2015-05-11 12:31:16 -07:00
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
markAsCheckOnce(): void { this.cdMode = ChangeDetectionStrategy.CheckOnce; }
|
2015-06-24 13:46:39 -07:00
|
|
|
|
2016-01-06 14:13:44 -08: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-12-02 17:09:46 -08:00
|
|
|
}
|
2014-09-28 16:29:11 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
private _resetDebug() { this._currentDebugContext = null; }
|
|
|
|
|
|
|
|
debug(nodeIndex: number, rowNum: number, colNum: number): DebugContext {
|
|
|
|
return this._currentDebugContext = new DebugContext(this, nodeIndex, rowNum, colNum);
|
2015-07-23 18:01:34 -07:00
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
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);
|
2015-12-02 10:35:51 -08:00
|
|
|
}
|
|
|
|
}
|
2015-07-27 15:47:42 -07:00
|
|
|
}
|
2015-12-02 10:35:51 -08:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2015-07-27 15:47:42 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
throwDestroyedError(details: string): void { throw new ViewDestroyedException(details); }
|
|
|
|
}
|
2015-12-02 10:35:51 -08:00
|
|
|
|
|
|
|
@CONST()
|
|
|
|
export class HostViewFactory {
|
|
|
|
constructor(public selector: string, public viewFactory: Function) {}
|
2015-10-01 10:07:49 -07:00
|
|
|
}
|
2015-07-27 15:47:42 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
function _findLastRenderNode(node: any): any {
|
2016-02-03 16:35:38 -08:00
|
|
|
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) {
|
2016-01-06 14:13:44 -08:00
|
|
|
lastNode = _findLastRenderNode(
|
2016-02-03 16:35:38 -08:00
|
|
|
nestedView.rootNodesOrAppElements[nestedView.rootNodesOrAppElements.length - 1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
lastNode = node;
|
|
|
|
}
|
|
|
|
return lastNode;
|
|
|
|
}
|