2017-01-20 13:10:57 -08:00
|
|
|
/**
|
|
|
|
|
* @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
|
|
|
|
|
*/
|
|
|
|
|
|
2017-01-26 17:07:37 -08:00
|
|
|
import {isDevMode} from '../application_ref';
|
2017-01-31 11:08:29 -08:00
|
|
|
import {WrappedValue, devModeEqual} from '../change_detection/change_detection';
|
2017-01-20 13:10:57 -08:00
|
|
|
import {SimpleChange} from '../change_detection/change_detection_util';
|
2017-02-01 11:32:27 -08:00
|
|
|
import {Injector} from '../di';
|
2017-02-03 15:20:50 -08:00
|
|
|
import {looseIdentical, stringify} from '../facade/lang';
|
2017-02-01 11:32:27 -08:00
|
|
|
import {TemplateRef} from '../linker/template_ref';
|
|
|
|
|
import {ViewContainerRef} from '../linker/view_container_ref';
|
|
|
|
|
import {ViewRef} from '../linker/view_ref';
|
2017-01-20 13:10:57 -08:00
|
|
|
import {Renderer} from '../render/api';
|
|
|
|
|
|
2017-01-31 14:52:01 -08:00
|
|
|
import {expressionChangedAfterItHasBeenCheckedError, isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
|
2017-02-03 15:20:50 -08:00
|
|
|
import {DebugContext, ElementData, NodeData, NodeDef, NodeFlags, NodeType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types';
|
2017-01-20 13:10:57 -08:00
|
|
|
|
2017-02-03 15:20:50 -08:00
|
|
|
const _tokenKeyCache = new Map<any, string>();
|
|
|
|
|
|
|
|
|
|
export function tokenKey(token: any): string {
|
|
|
|
|
let key = _tokenKeyCache.get(token);
|
|
|
|
|
if (!key) {
|
|
|
|
|
key = stringify(token) + '_' + _tokenKeyCache.size;
|
|
|
|
|
_tokenKeyCache.set(token, key);
|
2017-01-20 13:10:57 -08:00
|
|
|
}
|
2017-02-03 15:20:50 -08:00
|
|
|
return key;
|
2017-01-20 13:10:57 -08:00
|
|
|
}
|
|
|
|
|
|
2017-02-09 14:59:57 -08:00
|
|
|
let unwrapCounter = 0;
|
|
|
|
|
|
|
|
|
|
export function unwrapValue(value: any): any {
|
|
|
|
|
if (value instanceof WrappedValue) {
|
|
|
|
|
value = value.wrapped;
|
|
|
|
|
unwrapCounter++;
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-03 15:20:50 -08:00
|
|
|
export function checkBinding(
|
|
|
|
|
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
|
|
|
|
|
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
2017-02-09 14:59:57 -08:00
|
|
|
return unwrapCounter > 0 || !!(view.state & ViewState.FirstCheck) ||
|
|
|
|
|
!devModeEqual(oldValue, value);
|
2017-01-20 13:10:57 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function checkBindingNoChanges(
|
|
|
|
|
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
|
|
|
|
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
2017-02-09 14:59:57 -08:00
|
|
|
if (unwrapCounter || (view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value)) {
|
|
|
|
|
unwrapCounter = 0;
|
2017-01-26 17:07:37 -08:00
|
|
|
throw expressionChangedAfterItHasBeenCheckedError(
|
2017-02-03 15:20:50 -08:00
|
|
|
Services.createDebugContext(view, def.index), oldValue, value,
|
2017-02-01 11:32:27 -08:00
|
|
|
(view.state & ViewState.FirstCheck) !== 0);
|
2017-01-20 13:10:57 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function checkAndUpdateBinding(
|
|
|
|
|
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
|
|
|
|
|
const oldValues = view.oldValues;
|
2017-02-09 14:59:57 -08:00
|
|
|
if (unwrapCounter || (view.state & ViewState.FirstCheck) ||
|
2017-01-31 14:52:01 -08:00
|
|
|
!looseIdentical(oldValues[def.bindingIndex + bindingIdx], value)) {
|
2017-02-09 14:59:57 -08:00
|
|
|
unwrapCounter = 0;
|
2017-01-20 13:10:57 -08:00
|
|
|
oldValues[def.bindingIndex + bindingIdx] = value;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-31 14:52:01 -08:00
|
|
|
export function dispatchEvent(
|
|
|
|
|
view: ViewData, nodeIndex: number, eventName: string, event: any): boolean {
|
|
|
|
|
let currView = view;
|
|
|
|
|
while (currView) {
|
2017-02-01 11:32:27 -08:00
|
|
|
if (currView.def.flags & ViewFlags.OnPush) {
|
|
|
|
|
currView.state |= ViewState.ChecksEnabled;
|
2017-01-31 14:52:01 -08:00
|
|
|
}
|
|
|
|
|
currView = currView.parent;
|
2017-01-20 13:10:57 -08:00
|
|
|
}
|
2017-02-03 15:20:50 -08:00
|
|
|
return Services.handleEvent(view, nodeIndex, eventName, event);
|
2017-01-20 13:10:57 -08:00
|
|
|
}
|
2017-01-23 16:59:20 -08:00
|
|
|
|
2017-01-25 13:45:07 -08:00
|
|
|
export function declaredViewContainer(view: ViewData): ElementData {
|
2017-01-23 16:59:20 -08:00
|
|
|
if (view.parent) {
|
|
|
|
|
const parentView = view.parent;
|
2017-01-25 13:45:07 -08:00
|
|
|
return asElementData(parentView, view.parentIndex);
|
2017-01-23 16:59:20 -08:00
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
2017-01-25 13:45:07 -08:00
|
|
|
|
2017-02-01 11:32:27 -08:00
|
|
|
/**
|
2017-02-09 14:59:57 -08:00
|
|
|
* for component views, this is the host element.
|
2017-02-01 11:32:27 -08:00
|
|
|
* for embedded views, this is the index of the parent node
|
|
|
|
|
* that contains the view container.
|
|
|
|
|
*/
|
2017-02-09 14:59:57 -08:00
|
|
|
export function viewParentElIndex(view: ViewData): number {
|
|
|
|
|
const parentView = view.parent;
|
|
|
|
|
if (parentView) {
|
|
|
|
|
return parentView.def.nodes[view.parentIndex].parent;
|
|
|
|
|
} else {
|
|
|
|
|
return null;
|
2017-02-01 11:32:27 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-02 15:01:35 -08:00
|
|
|
export function renderNode(view: ViewData, def: NodeDef): any {
|
|
|
|
|
switch (def.type) {
|
|
|
|
|
case NodeType.Element:
|
|
|
|
|
return asElementData(view, def.index).renderElement;
|
|
|
|
|
case NodeType.Text:
|
|
|
|
|
return asTextData(view, def.index).renderText;
|
2017-02-01 07:27:38 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-02 15:01:35 -08:00
|
|
|
export function nodeValue(view: ViewData, index: number): any {
|
|
|
|
|
const def = view.def.nodes[index];
|
2017-01-25 13:45:07 -08:00
|
|
|
switch (def.type) {
|
|
|
|
|
case NodeType.Element:
|
|
|
|
|
return asElementData(view, def.index).renderElement;
|
|
|
|
|
case NodeType.Text:
|
|
|
|
|
return asTextData(view, def.index).renderText;
|
2017-02-09 14:59:57 -08:00
|
|
|
case NodeType.Directive:
|
|
|
|
|
case NodeType.Pipe:
|
2017-02-02 15:01:35 -08:00
|
|
|
case NodeType.Provider:
|
|
|
|
|
return asProviderData(view, def.index).instance;
|
2017-01-25 13:45:07 -08:00
|
|
|
}
|
2017-02-02 15:01:35 -08:00
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function queryIdIsReference(queryId: string): boolean {
|
|
|
|
|
return queryId.startsWith('#');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function elementEventFullName(target: string, name: string): string {
|
|
|
|
|
return target ? `${target}:${name}` : name;
|
2017-01-26 17:07:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isComponentView(view: ViewData): boolean {
|
|
|
|
|
return view.component === view.context && !!view.parent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const VIEW_DEFINITION_CACHE = new WeakMap<any, ViewDefinition>();
|
|
|
|
|
|
|
|
|
|
export function resolveViewDefinition(factory: ViewDefinitionFactory): ViewDefinition {
|
|
|
|
|
let value: ViewDefinition = VIEW_DEFINITION_CACHE.get(factory);
|
|
|
|
|
if (!value) {
|
|
|
|
|
value = factory();
|
|
|
|
|
VIEW_DEFINITION_CACHE.set(factory, value);
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function sliceErrorStack(start: number, end: number): string {
|
|
|
|
|
let err: any;
|
|
|
|
|
try {
|
|
|
|
|
throw new Error();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
err = e;
|
|
|
|
|
}
|
|
|
|
|
const stack = err.stack || '';
|
|
|
|
|
const lines = stack.split('\n');
|
|
|
|
|
if (lines[0].startsWith('Error')) {
|
|
|
|
|
// Chrome always adds the message to the stack as well...
|
|
|
|
|
start++;
|
|
|
|
|
end++;
|
|
|
|
|
}
|
|
|
|
|
return lines.slice(start, end).join('\n');
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-31 08:51:42 -08:00
|
|
|
export function rootRenderNodes(view: ViewData): any[] {
|
|
|
|
|
const renderNodes: any[] = [];
|
|
|
|
|
visitRootRenderNodes(view, RenderNodeAction.Collect, undefined, undefined, renderNodes);
|
|
|
|
|
return renderNodes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export enum RenderNodeAction {
|
|
|
|
|
Collect,
|
|
|
|
|
AppendChild,
|
|
|
|
|
InsertBefore,
|
|
|
|
|
RemoveChild
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function visitRootRenderNodes(
|
|
|
|
|
view: ViewData, action: RenderNodeAction, parentNode: any, nextSibling: any, target: any[]) {
|
2017-02-14 21:03:18 -08:00
|
|
|
// We need to re-compute the parent node in case the nodes have been moved around manually
|
|
|
|
|
if (action === RenderNodeAction.RemoveChild) {
|
|
|
|
|
parentNode = view.root.renderer.parentNode(renderNode(view, view.def.lastRootNode));
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-31 08:51:42 -08:00
|
|
|
const len = view.def.nodes.length;
|
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
|
|
|
const nodeDef = view.def.nodes[i];
|
2017-02-09 14:59:57 -08:00
|
|
|
if (nodeDef.type === NodeType.Element || nodeDef.type === NodeType.Text ||
|
|
|
|
|
nodeDef.type === NodeType.NgContent) {
|
|
|
|
|
visitRenderNode(view, nodeDef, action, parentNode, nextSibling, target);
|
|
|
|
|
}
|
2017-01-31 08:51:42 -08:00
|
|
|
// jump to next sibling
|
|
|
|
|
i += nodeDef.childCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function visitProjectedRenderNodes(
|
|
|
|
|
view: ViewData, ngContentIndex: number, action: RenderNodeAction, parentNode: any,
|
|
|
|
|
nextSibling: any, target: any[]) {
|
|
|
|
|
let compView = view;
|
2017-02-01 11:32:27 -08:00
|
|
|
while (compView && !isComponentView(compView)) {
|
2017-01-31 08:51:42 -08:00
|
|
|
compView = compView.parent;
|
|
|
|
|
}
|
|
|
|
|
const hostView = compView.parent;
|
2017-02-09 14:59:57 -08:00
|
|
|
const hostElDef = hostView.def.nodes[viewParentElIndex(compView)];
|
2017-01-31 08:51:42 -08:00
|
|
|
const startIndex = hostElDef.index + 1;
|
|
|
|
|
const endIndex = hostElDef.index + hostElDef.childCount;
|
|
|
|
|
for (let i = startIndex; i <= endIndex; i++) {
|
|
|
|
|
const nodeDef = hostView.def.nodes[i];
|
|
|
|
|
if (nodeDef.ngContentIndex === ngContentIndex) {
|
|
|
|
|
visitRenderNode(hostView, nodeDef, action, parentNode, nextSibling, target);
|
|
|
|
|
}
|
|
|
|
|
// jump to next sibling
|
|
|
|
|
i += nodeDef.childCount;
|
|
|
|
|
}
|
2017-02-01 11:32:27 -08:00
|
|
|
if (!hostView.parent) {
|
|
|
|
|
// a root view
|
|
|
|
|
const projectedNodes = view.root.projectableNodes[ngContentIndex];
|
|
|
|
|
if (projectedNodes) {
|
|
|
|
|
for (let i = 0; i < projectedNodes.length; i++) {
|
2017-02-03 15:20:50 -08:00
|
|
|
execRenderNodeAction(view, projectedNodes[i], action, parentNode, nextSibling, target);
|
2017-02-01 11:32:27 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-31 08:51:42 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function visitRenderNode(
|
|
|
|
|
view: ViewData, nodeDef: NodeDef, action: RenderNodeAction, parentNode: any, nextSibling: any,
|
|
|
|
|
target: any[]) {
|
|
|
|
|
if (nodeDef.type === NodeType.NgContent) {
|
|
|
|
|
visitProjectedRenderNodes(
|
|
|
|
|
view, nodeDef.ngContent.index, action, parentNode, nextSibling, target);
|
|
|
|
|
} else {
|
|
|
|
|
const rn = renderNode(view, nodeDef);
|
2017-02-03 15:20:50 -08:00
|
|
|
execRenderNodeAction(view, rn, action, parentNode, nextSibling, target);
|
2017-01-31 08:51:42 -08:00
|
|
|
if (nodeDef.flags & NodeFlags.HasEmbeddedViews) {
|
|
|
|
|
const embeddedViews = asElementData(view, nodeDef.index).embeddedViews;
|
|
|
|
|
if (embeddedViews) {
|
|
|
|
|
for (let k = 0; k < embeddedViews.length; k++) {
|
|
|
|
|
visitRootRenderNodes(embeddedViews[k], action, parentNode, nextSibling, target);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-01 11:32:27 -08:00
|
|
|
|
|
|
|
|
function execRenderNodeAction(
|
2017-02-03 15:20:50 -08:00
|
|
|
view: ViewData, renderNode: any, action: RenderNodeAction, parentNode: any, nextSibling: any,
|
|
|
|
|
target: any[]) {
|
|
|
|
|
const renderer = view.root.renderer;
|
2017-02-01 11:32:27 -08:00
|
|
|
switch (action) {
|
|
|
|
|
case RenderNodeAction.AppendChild:
|
2017-02-03 15:20:50 -08:00
|
|
|
renderer.appendChild(parentNode, renderNode);
|
2017-02-01 11:32:27 -08:00
|
|
|
break;
|
|
|
|
|
case RenderNodeAction.InsertBefore:
|
2017-02-03 15:20:50 -08:00
|
|
|
renderer.insertBefore(parentNode, renderNode, nextSibling);
|
2017-02-01 11:32:27 -08:00
|
|
|
break;
|
|
|
|
|
case RenderNodeAction.RemoveChild:
|
2017-02-03 15:20:50 -08:00
|
|
|
renderer.removeChild(parentNode, renderNode);
|
2017-02-01 11:32:27 -08:00
|
|
|
break;
|
|
|
|
|
case RenderNodeAction.Collect:
|
|
|
|
|
target.push(renderNode);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|