2017-12-01 14:23:03 -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
|
|
|
|
*/
|
|
|
|
|
|
|
|
import './ng_dev_mode';
|
|
|
|
|
|
|
|
import {assertEqual, assertLessThan, assertNotEqual, assertNotNull} from './assert';
|
2018-01-10 18:19:16 -08:00
|
|
|
import {LContainer, TContainer} from './interfaces/container';
|
2018-01-09 18:38:17 -08:00
|
|
|
import {CssSelector, LProjection} from './interfaces/projection';
|
|
|
|
import {LQuery, QueryReadType} from './interfaces/query';
|
2018-01-23 18:39:09 -08:00
|
|
|
import {LView, LifecycleStage, TData, TView} from './interfaces/view';
|
2017-12-14 15:03:46 -08:00
|
|
|
|
2018-01-10 18:19:16 -08:00
|
|
|
import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node';
|
2018-01-17 10:09:05 -08:00
|
|
|
import {assertNodeType, assertNodeOfPossibleTypes} from './node_assert';
|
2017-12-01 14:23:03 -08:00
|
|
|
import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation';
|
|
|
|
import {isNodeMatchingSelector} from './node_selector_matcher';
|
2018-01-22 15:27:21 -08:00
|
|
|
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType} from './interfaces/definition';
|
2018-01-25 15:32:21 +01:00
|
|
|
import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3} from './interfaces/renderer';
|
2017-12-01 14:23:03 -08:00
|
|
|
import {isDifferent, stringify} from './util';
|
2018-01-22 19:19:47 -08:00
|
|
|
import {executeViewHooks, executeContentHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
|
2017-12-14 15:50:01 -08:00
|
|
|
/**
|
2017-12-19 16:51:42 +01:00
|
|
|
* Directive (D) sets a property on all component instances using this constant as a key and the
|
2017-12-14 16:26:28 -08:00
|
|
|
* component's host node (LElement) as the value. This is used in methods like detectChanges to
|
|
|
|
* facilitate jumping from an instance to the host node.
|
2017-12-14 15:50:01 -08:00
|
|
|
*/
|
2017-12-01 14:23:03 -08:00
|
|
|
export const NG_HOST_SYMBOL = '__ngHostLNode__';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This property gets set before entering a template.
|
2017-12-14 15:50:01 -08:00
|
|
|
*
|
|
|
|
* This renderer can be one of two varieties of Renderer3:
|
|
|
|
*
|
|
|
|
* - ObjectedOrientedRenderer3
|
|
|
|
*
|
2017-12-14 16:26:28 -08:00
|
|
|
* This is the native browser API style, e.g. operations are methods on individual objects
|
2017-12-14 15:03:46 -08:00
|
|
|
* like HTMLElement. With this style, no additional code is needed as a facade (reducing payload
|
|
|
|
* size).
|
2017-12-14 15:50:01 -08:00
|
|
|
*
|
|
|
|
* - ProceduralRenderer3
|
|
|
|
*
|
|
|
|
* In non-native browser environments (e.g. platforms such as web-workers), this is the facade
|
2017-12-14 16:26:28 -08:00
|
|
|
* that enables element manipulation. This also facilitates backwards compatibility with
|
2017-12-14 15:50:01 -08:00
|
|
|
* Renderer2.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
|
|
|
let renderer: Renderer3;
|
2017-12-11 16:30:46 +01:00
|
|
|
let rendererFactory: RendererFactory3;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
/** Used to set the parent property when nodes are created. */
|
|
|
|
let previousOrParentNode: LNode;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If `isParent` is:
|
|
|
|
* - `true`: then `previousOrParentNode` points to a parent node.
|
|
|
|
* - `false`: then `previousOrParentNode` points to previous node (sibling).
|
|
|
|
*/
|
|
|
|
let isParent: boolean;
|
|
|
|
|
|
|
|
/**
|
2018-01-10 18:19:16 -08:00
|
|
|
* Static data that corresponds to the instance-specific data array on an LView.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-01-10 18:19:16 -08:00
|
|
|
* Each node's static data is stored in tData at the same index that it's stored
|
|
|
|
* in the data array. Each directive's definition is stored here at the same index
|
|
|
|
* as its directive instance in the data array. Any nodes that do not have static
|
|
|
|
* data store a null value in tData to avoid a sparse array.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-01-10 18:19:16 -08:00
|
|
|
let tData: TData;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
/** State of the current view being processed. */
|
|
|
|
let currentView: LView;
|
|
|
|
// The initialization has to be after the `let`, otherwise `createLView` can't see `let`.
|
2018-01-22 17:43:52 -08:00
|
|
|
currentView = createLView(null !, null !, createTView());
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
let currentQuery: LQuery|null;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This property gets set before entering a template.
|
|
|
|
*/
|
|
|
|
let creationMode: boolean;
|
|
|
|
|
|
|
|
/**
|
2017-12-08 11:48:54 -08:00
|
|
|
* An array of nodes (text, element, container, etc), their bindings, and
|
|
|
|
* any local variables that need to be stored between invocations.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2017-12-08 11:48:54 -08:00
|
|
|
let data: any[];
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Points to the next binding index to read or write to.
|
|
|
|
*/
|
|
|
|
let bindingIndex: number;
|
|
|
|
|
|
|
|
/**
|
2018-01-23 10:57:48 -08:00
|
|
|
* When a view is destroyed, listeners need to be released and outputs need to be
|
|
|
|
* unsubscribed. This cleanup array stores both listener data (in chunks of 4)
|
|
|
|
* and output data (in chunks of 2) for a particular view. Combining the arrays
|
|
|
|
* saves on memory (70 bytes per array) and on a few bytes of code size (for two
|
|
|
|
* separate for loops).
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
|
|
|
* If it's a listener being stored:
|
|
|
|
* 1st index is: event name to remove
|
|
|
|
* 2nd index is: native element
|
|
|
|
* 3rd index is: listener function
|
|
|
|
* 4th index is: useCapture boolean
|
|
|
|
*
|
2018-01-23 10:57:48 -08:00
|
|
|
* If it's an output subscription:
|
|
|
|
* 1st index is: unsubscribe function
|
2017-12-01 14:23:03 -08:00
|
|
|
* 2nd index is: context for function
|
|
|
|
*/
|
|
|
|
let cleanup: any[]|null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Swap the current state with a new state.
|
|
|
|
*
|
|
|
|
* For performance reasons we store the state in the top level of the module.
|
|
|
|
* This way we minimize the number of properties to read. Whenever a new view
|
|
|
|
* is entered we have to store the state for later, and when the view is
|
|
|
|
* exited the state has to be restored
|
|
|
|
*
|
2018-01-08 20:17:13 -08:00
|
|
|
* @param newView New state to become active
|
2017-12-01 14:23:03 -08:00
|
|
|
* @param host Element to which the View is a child of
|
|
|
|
* @returns the previous state;
|
|
|
|
*/
|
2018-01-08 20:17:13 -08:00
|
|
|
export function enterView(newView: LView, host: LElementNode | LViewNode | null): LView {
|
|
|
|
const oldView = currentView;
|
|
|
|
data = newView.data;
|
|
|
|
bindingIndex = newView.bindingStartIndex || 0;
|
2018-01-10 18:19:16 -08:00
|
|
|
tData = newView.tView.data;
|
2018-01-08 20:17:13 -08:00
|
|
|
creationMode = newView.creationMode;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
cleanup = newView.cleanup;
|
|
|
|
renderer = newView.renderer;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
if (host != null) {
|
|
|
|
previousOrParentNode = host;
|
|
|
|
isParent = true;
|
|
|
|
}
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
currentView = newView;
|
2018-01-17 17:55:55 +01:00
|
|
|
currentQuery = newView.query;
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
return oldView !;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2017-12-14 15:50:01 -08:00
|
|
|
/**
|
|
|
|
* Used in lieu of enterView to make it clear when we are exiting a child view. This makes
|
|
|
|
* the direction of traversal (up or down the view tree) a bit clearer.
|
|
|
|
*/
|
2018-01-08 20:17:13 -08:00
|
|
|
export function leaveView(newView: LView): void {
|
2018-01-22 19:19:47 -08:00
|
|
|
executeViewHooks(currentView);
|
|
|
|
currentView.creationMode = false;
|
2018-01-23 18:39:09 -08:00
|
|
|
currentView.lifecycleStage = LifecycleStage.INIT;
|
2018-01-23 10:57:48 -08:00
|
|
|
currentView.tView.firstTemplatePass = false;
|
2018-01-08 20:17:13 -08:00
|
|
|
enterView(newView, null);
|
2017-12-22 16:41:34 -08:00
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-17 10:09:05 -08:00
|
|
|
export function createLView(
|
|
|
|
viewId: number, renderer: Renderer3, tView: TView,
|
|
|
|
template: ComponentTemplate<any>| null = null, context: any | null = null): LView {
|
2017-12-01 14:23:03 -08:00
|
|
|
const newView = {
|
|
|
|
parent: currentView,
|
2017-12-08 11:48:54 -08:00
|
|
|
id: viewId, // -1 for component views
|
|
|
|
node: null !, // until we initialize it in createNode.
|
2017-12-22 16:41:34 -08:00
|
|
|
data: [],
|
2018-01-10 18:19:16 -08:00
|
|
|
tView: tView,
|
2017-12-01 14:23:03 -08:00
|
|
|
cleanup: null,
|
|
|
|
renderer: renderer,
|
|
|
|
child: null,
|
|
|
|
tail: null,
|
|
|
|
next: null,
|
2017-12-22 16:41:34 -08:00
|
|
|
bindingStartIndex: null,
|
|
|
|
creationMode: true,
|
2018-01-17 10:09:05 -08:00
|
|
|
template: template,
|
|
|
|
context: context,
|
|
|
|
dynamicViewCount: 0,
|
2018-01-17 17:55:55 +01:00
|
|
|
lifecycleStage: LifecycleStage.INIT,
|
|
|
|
query: null,
|
2017-12-01 14:23:03 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
return newView;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A common way of creating the LNode to make sure that all of them have same shape to
|
|
|
|
* keep the execution code monomorphic and fast.
|
|
|
|
*/
|
2017-12-08 11:48:54 -08:00
|
|
|
export function createLNode(
|
2017-12-01 14:23:03 -08:00
|
|
|
index: number | null, type: LNodeFlags.Element, native: RElement | RText | null,
|
2018-01-08 20:17:13 -08:00
|
|
|
lView?: LView | null): LElementNode;
|
2017-12-08 11:48:54 -08:00
|
|
|
export function createLNode(
|
2018-01-08 20:17:13 -08:00
|
|
|
index: null, type: LNodeFlags.View, native: null, lView: LView): LViewNode;
|
2017-12-08 11:48:54 -08:00
|
|
|
export function createLNode(
|
2018-01-25 15:32:21 +01:00
|
|
|
index: number, type: LNodeFlags.Container, native: null,
|
2018-01-08 20:17:13 -08:00
|
|
|
lContainer: LContainer): LContainerNode;
|
2017-12-08 11:48:54 -08:00
|
|
|
export function createLNode(
|
2017-12-01 14:23:03 -08:00
|
|
|
index: number, type: LNodeFlags.Projection, native: null,
|
2018-01-08 20:17:13 -08:00
|
|
|
lProjection: LProjection): LProjectionNode;
|
2017-12-08 11:48:54 -08:00
|
|
|
export function createLNode(
|
2018-01-25 15:32:21 +01:00
|
|
|
index: number | null, type: LNodeFlags, native: RText | RElement | null, state?: null | LView |
|
|
|
|
LContainer | LProjection): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode {
|
2017-12-01 14:23:03 -08:00
|
|
|
const parent = isParent ? previousOrParentNode :
|
|
|
|
previousOrParentNode && previousOrParentNode.parent as LNode;
|
|
|
|
let query = (isParent ? currentQuery : previousOrParentNode && previousOrParentNode.query) ||
|
|
|
|
parent && parent.query && parent.query.child();
|
|
|
|
const isState = state != null;
|
2018-01-08 20:17:13 -08:00
|
|
|
const node: LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode = {
|
2017-12-01 14:23:03 -08:00
|
|
|
flags: type,
|
|
|
|
native: native as any,
|
|
|
|
view: currentView,
|
|
|
|
parent: parent as any,
|
|
|
|
child: null,
|
|
|
|
next: null,
|
|
|
|
nodeInjector: parent ? parent.nodeInjector : null,
|
|
|
|
data: isState ? state as any : null,
|
|
|
|
query: query,
|
2018-01-25 15:32:21 +01:00
|
|
|
tNode: null,
|
|
|
|
pNextOrParent: null
|
2017-12-01 14:23:03 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
if ((type & LNodeFlags.ViewOrElement) === LNodeFlags.ViewOrElement && isState) {
|
|
|
|
// Bit of a hack to bust through the readonly because there is a circular dep between
|
2018-01-08 20:17:13 -08:00
|
|
|
// LView and LNode.
|
|
|
|
ngDevMode && assertEqual((state as LView).node, null, 'lView.node');
|
|
|
|
(state as LView as{node: LNode}).node = node;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
if (index != null) {
|
|
|
|
// We are Element or Container
|
2018-01-18 13:27:01 -08:00
|
|
|
ngDevMode && assertDataNext(index);
|
2017-12-08 11:48:54 -08:00
|
|
|
data[index] = node;
|
|
|
|
|
|
|
|
// Every node adds a value to the static data array to avoid a sparse array
|
2018-01-10 18:19:16 -08:00
|
|
|
if (index >= tData.length) {
|
|
|
|
tData[index] = null;
|
2017-12-11 14:08:52 -08:00
|
|
|
} else {
|
2018-01-10 18:19:16 -08:00
|
|
|
node.tNode = tData[index] as TNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now link ourselves into the tree.
|
|
|
|
if (isParent) {
|
|
|
|
currentQuery = null;
|
2017-12-08 11:48:54 -08:00
|
|
|
if (previousOrParentNode.view === currentView ||
|
|
|
|
(previousOrParentNode.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.View) {
|
2017-12-01 14:23:03 -08:00
|
|
|
// We are in the same view, which means we are adding content node to the parent View.
|
|
|
|
ngDevMode && assertEqual(previousOrParentNode.child, null, 'previousNode.child');
|
|
|
|
previousOrParentNode.child = node;
|
|
|
|
} else {
|
|
|
|
// We are adding component view, so we don't link parent node child to this node.
|
|
|
|
}
|
|
|
|
} else if (previousOrParentNode) {
|
|
|
|
ngDevMode && assertEqual(previousOrParentNode.next, null, 'previousNode.next');
|
|
|
|
previousOrParentNode.next = node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
previousOrParentNode = node;
|
|
|
|
isParent = true;
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////
|
|
|
|
//// Render
|
|
|
|
//////////////////////////
|
|
|
|
|
2018-01-03 10:45:09 +01:00
|
|
|
/**
|
|
|
|
* Resets the application state.
|
|
|
|
*/
|
|
|
|
function resetApplicationState() {
|
|
|
|
isParent = false;
|
|
|
|
previousOrParentNode = null !;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param host Existing node to render into.
|
|
|
|
* @param template Template function with the instructions.
|
|
|
|
* @param context to pass into the template.
|
|
|
|
*/
|
2017-12-11 16:30:46 +01:00
|
|
|
export function renderTemplate<T>(
|
|
|
|
hostNode: RElement, template: ComponentTemplate<T>, context: T,
|
2018-01-08 20:17:13 -08:00
|
|
|
providedRendererFactory: RendererFactory3, host: LElementNode | null): LElementNode {
|
2017-12-11 16:30:46 +01:00
|
|
|
if (host == null) {
|
2018-01-03 10:45:09 +01:00
|
|
|
resetApplicationState();
|
2017-12-11 16:30:46 +01:00
|
|
|
rendererFactory = providedRendererFactory;
|
|
|
|
host = createLNode(
|
|
|
|
null, LNodeFlags.Element, hostNode,
|
2018-01-10 18:19:16 -08:00
|
|
|
createLView(
|
|
|
|
-1, providedRendererFactory.createRenderer(null, null), getOrCreateTView(template)));
|
2017-12-11 16:30:46 +01:00
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
const hostView = host.data !;
|
|
|
|
ngDevMode && assertNotEqual(hostView, null, 'hostView');
|
2017-12-11 16:30:46 +01:00
|
|
|
renderComponentOrTemplate(host, hostView, context, template);
|
|
|
|
return host;
|
|
|
|
}
|
|
|
|
|
2018-01-17 10:09:05 -08:00
|
|
|
export function renderEmbeddedTemplate<T>(
|
|
|
|
viewNode: LViewNode | null, template: ComponentTemplate<T>, context: T,
|
|
|
|
renderer: Renderer3): LViewNode {
|
|
|
|
const _isParent = isParent;
|
|
|
|
const _previousOrParentNode = previousOrParentNode;
|
|
|
|
try {
|
|
|
|
isParent = true;
|
|
|
|
previousOrParentNode = null !;
|
|
|
|
let cm: boolean = false;
|
|
|
|
if (viewNode == null) {
|
2018-01-23 10:57:48 -08:00
|
|
|
const view = createLView(-1, renderer, createTView(), template, context);
|
2018-01-17 10:09:05 -08:00
|
|
|
viewNode = createLNode(null, LNodeFlags.View, null, view);
|
|
|
|
cm = true;
|
|
|
|
}
|
|
|
|
enterView(viewNode.data, viewNode);
|
|
|
|
|
|
|
|
template(context, cm);
|
|
|
|
} finally {
|
|
|
|
refreshDynamicChildren();
|
|
|
|
leaveView(currentView !.parent !);
|
|
|
|
isParent = _isParent;
|
|
|
|
previousOrParentNode = _previousOrParentNode;
|
|
|
|
}
|
|
|
|
return viewNode;
|
|
|
|
}
|
|
|
|
|
2017-12-11 16:30:46 +01:00
|
|
|
export function renderComponentOrTemplate<T>(
|
2018-01-10 18:19:16 -08:00
|
|
|
node: LElementNode, hostView: LView, componentOrContext: T, template?: ComponentTemplate<T>) {
|
|
|
|
const oldView = enterView(hostView, node);
|
2017-12-01 14:23:03 -08:00
|
|
|
try {
|
2017-12-11 16:30:46 +01:00
|
|
|
if (rendererFactory.begin) {
|
|
|
|
rendererFactory.begin();
|
|
|
|
}
|
|
|
|
if (template) {
|
|
|
|
template(componentOrContext !, creationMode);
|
|
|
|
} else {
|
|
|
|
// Element was stored at 0 and directive was stored at 1 in renderComponent
|
2018-01-22 19:52:06 -08:00
|
|
|
// so to refresh the component, refresh() needs to be called with (1, 0)
|
|
|
|
componentRefresh(1, 0);
|
2017-12-11 16:30:46 +01:00
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
} finally {
|
2017-12-11 16:30:46 +01:00
|
|
|
if (rendererFactory.end) {
|
|
|
|
rendererFactory.end();
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
leaveView(oldView);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////
|
2017-12-14 15:50:01 -08:00
|
|
|
//// Element
|
2017-12-01 14:23:03 -08:00
|
|
|
//////////////////////////
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create DOM element. The instruction must later be followed by `elementEnd()` call.
|
|
|
|
*
|
2017-12-08 11:48:54 -08:00
|
|
|
* @param index Index of the element in the data array
|
2018-01-08 21:57:50 -08:00
|
|
|
* @param nameOrComponentType Name of the DOM Node or `ComponentType` to create.
|
2017-12-01 14:23:03 -08:00
|
|
|
* @param attrs Statically bound set of attributes to be written into the DOM element on creation.
|
2018-01-08 21:57:50 -08:00
|
|
|
* @param directiveTypes A set of directives declared on this element.
|
|
|
|
* @param localRefs A set of local reference bindings on the element.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-01-08 21:57:50 -08:00
|
|
|
* Attributes and localRefs are passed as an array of strings where elements with an even index
|
|
|
|
* hold an attribute name and elements with an odd index hold an attribute value, ex.:
|
2017-12-01 14:23:03 -08:00
|
|
|
* ['id', 'warning5', 'class', 'alert']
|
|
|
|
*/
|
2017-12-14 16:26:28 -08:00
|
|
|
export function elementStart(
|
2018-01-08 21:57:50 -08:00
|
|
|
index: number, nameOrComponentType?: string | ComponentType<any>, attrs?: string[] | null,
|
|
|
|
directiveTypes?: DirectiveType<any>[] | null, localRefs?: string[] | null): RElement {
|
2018-01-08 20:17:13 -08:00
|
|
|
let node: LElementNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
let native: RElement;
|
|
|
|
|
2018-01-08 21:57:50 -08:00
|
|
|
if (nameOrComponentType == null) {
|
2017-12-01 14:23:03 -08:00
|
|
|
// native node retrieval - used for exporting elements as tpl local variables (<div #foo>)
|
2017-12-08 11:48:54 -08:00
|
|
|
const node = data[index] !;
|
2018-01-08 20:17:13 -08:00
|
|
|
native = node && (node as LElementNode).native;
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
|
|
|
ngDevMode && assertEqual(currentView.bindingStartIndex, null, 'bindingStartIndex');
|
2018-01-08 21:57:50 -08:00
|
|
|
const isHostElement = typeof nameOrComponentType !== 'string';
|
|
|
|
// MEGAMORPHIC: `ngComponentDef` is a megamorphic property access here.
|
|
|
|
// This is OK, since we will refactor this code and store the result in `TView.data`
|
|
|
|
// which means that we will be reading this value only once. We are trading clean/simple
|
|
|
|
// template
|
|
|
|
// code for slight startup(first run) performance. (No impact on subsequent runs)
|
|
|
|
// TODO(misko): refactor this to store the `ComponentDef` in `TView.data`.
|
|
|
|
const hostComponentDef =
|
|
|
|
isHostElement ? (nameOrComponentType as ComponentType<any>).ngComponentDef : null;
|
|
|
|
const name = isHostElement ? hostComponentDef !.tag : nameOrComponentType as string;
|
2017-12-01 14:23:03 -08:00
|
|
|
if (name === null) {
|
|
|
|
// TODO: future support for nameless components.
|
|
|
|
throw 'for now name is required';
|
|
|
|
} else {
|
|
|
|
native = renderer.createElement(name);
|
2017-12-11 14:08:52 -08:00
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
let componentView: LView|null = null;
|
2017-12-11 14:08:52 -08:00
|
|
|
if (isHostElement) {
|
2018-01-10 18:19:16 -08:00
|
|
|
const tView = getOrCreateTView(hostComponentDef !.template);
|
2018-01-08 20:17:13 -08:00
|
|
|
componentView = addToViewTree(createLView(
|
2018-01-10 18:19:16 -08:00
|
|
|
-1, rendererFactory.createRenderer(native, hostComponentDef !.rendererType), tView));
|
2017-12-11 14:08:52 -08:00
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
// Only component views should be added to the view tree directly. Embedded views are
|
|
|
|
// accessed through their containers because they may be removed / re-added later.
|
2017-12-11 14:08:52 -08:00
|
|
|
node = createLNode(index, LNodeFlags.Element, native, componentView);
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-08 21:57:50 -08:00
|
|
|
// TODO(misko): implement code which caches the local reference resolution
|
|
|
|
const queryName: string|null = hack_findQueryName(hostComponentDef, localRefs, '');
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
if (node.tNode == null) {
|
2017-12-11 14:08:52 -08:00
|
|
|
ngDevMode && assertDataInRange(index - 1);
|
2018-01-10 18:19:16 -08:00
|
|
|
node.tNode = tData[index] =
|
2018-01-08 21:57:50 -08:00
|
|
|
createTNode(name, attrs || null, null, hostComponentDef ? null : queryName);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (attrs) setUpAttributes(native, attrs);
|
|
|
|
appendChild(node.parent !, native, currentView);
|
2018-01-08 21:57:50 -08:00
|
|
|
|
|
|
|
if (hostComponentDef) {
|
|
|
|
// TODO(mhevery): This assumes that the directives come in correct order, which
|
|
|
|
// is not guaranteed. Must be refactored to take it into account.
|
|
|
|
directiveCreate(++index, hostComponentDef.n(), hostComponentDef, queryName);
|
|
|
|
}
|
|
|
|
hack_declareDirectives(index, directiveTypes, localRefs);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return native;
|
|
|
|
}
|
|
|
|
|
2018-01-08 21:57:50 -08:00
|
|
|
/**
|
|
|
|
* This function instantiates a directive with a correct queryName. It is a hack since we should
|
|
|
|
* compute the query value only once and store it with the template (rather than on each invocation)
|
|
|
|
*/
|
|
|
|
function hack_declareDirectives(
|
|
|
|
index: number, directiveTypes: DirectiveType<any>[] | null | undefined,
|
|
|
|
localRefs: string[] | null | undefined, ) {
|
|
|
|
if (directiveTypes) {
|
|
|
|
// TODO(mhevery): This assumes that the directives come in correct order, which
|
|
|
|
// is not guaranteed. Must be refactored to take it into account.
|
|
|
|
for (let i = 0; i < directiveTypes.length; i++) {
|
|
|
|
// MEGAMORPHIC: `ngDirectiveDef` is a megamorphic property access here.
|
|
|
|
// This is OK, since we will refactor this code and store the result in `TView.data`
|
|
|
|
// which means that we will be reading this value only once. We are trading clean/simple
|
|
|
|
// template
|
|
|
|
// code for slight startup(first run) performance. (No impact on subsequent runs)
|
|
|
|
// TODO(misko): refactor this to store the `DirectiveDef` in `TView.data`.
|
2018-01-09 16:43:12 -08:00
|
|
|
const directiveType = directiveTypes[i];
|
|
|
|
const directiveDef = directiveType.ngDirectiveDef;
|
2018-01-08 21:57:50 -08:00
|
|
|
directiveCreate(
|
|
|
|
++index, directiveDef.n(), directiveDef, hack_findQueryName(directiveDef, localRefs));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function returns the queryName for a directive. It is a hack since we should
|
|
|
|
* compute the query value only once and store it with the template (rather than on each invocation)
|
|
|
|
*/
|
|
|
|
function hack_findQueryName(
|
|
|
|
directiveDef: DirectiveDef<any>| null, localRefs: string[] | null | undefined,
|
|
|
|
defaultExport?: string, ): string|null {
|
|
|
|
const exportAs = directiveDef && directiveDef.exportAs || defaultExport;
|
|
|
|
if (exportAs != null && localRefs) {
|
|
|
|
for (let i = 0; i < localRefs.length; i = i + 2) {
|
|
|
|
const local = localRefs[i];
|
|
|
|
const toExportAs = localRefs[i | 1];
|
|
|
|
if (toExportAs === exportAs || toExportAs === defaultExport) {
|
|
|
|
return local;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-12-11 14:08:52 -08:00
|
|
|
/**
|
2018-01-10 18:19:16 -08:00
|
|
|
* Gets TView from a template function or creates a new TView
|
|
|
|
* if it doesn't already exist.
|
2017-12-11 14:08:52 -08:00
|
|
|
*
|
|
|
|
* @param template The template from which to get static data
|
2018-01-10 18:19:16 -08:00
|
|
|
* @returns TView
|
2017-12-11 14:08:52 -08:00
|
|
|
*/
|
2018-01-10 18:19:16 -08:00
|
|
|
function getOrCreateTView(template: ComponentTemplate<any>): TView {
|
2018-01-22 17:43:52 -08:00
|
|
|
return template.ngPrivateData || (template.ngPrivateData = createTView() as never);
|
|
|
|
}
|
|
|
|
|
2018-01-23 10:57:48 -08:00
|
|
|
/** Creates a TView instance */
|
2018-01-22 17:43:52 -08:00
|
|
|
export function createTView(): TView {
|
2018-01-23 10:57:48 -08:00
|
|
|
return {
|
|
|
|
data: [],
|
|
|
|
firstTemplatePass: true,
|
|
|
|
initHooks: null,
|
|
|
|
contentHooks: null,
|
|
|
|
viewHooks: null,
|
|
|
|
destroyHooks: null
|
|
|
|
};
|
2017-12-11 14:08:52 -08:00
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
function setUpAttributes(native: RElement, attrs: string[]): void {
|
|
|
|
ngDevMode && assertEqual(attrs.length % 2, 0, 'attrs.length % 2');
|
2017-12-14 16:26:28 -08:00
|
|
|
const isProceduralRenderer = (renderer as ProceduralRenderer3).setAttribute;
|
2017-12-01 14:23:03 -08:00
|
|
|
for (let i = 0; i < attrs.length; i += 2) {
|
2017-12-14 15:03:46 -08:00
|
|
|
isProceduralRenderer ?
|
|
|
|
(renderer as ProceduralRenderer3).setAttribute !(native, attrs[i], attrs[i | 1]) :
|
|
|
|
native.setAttribute(attrs[i], attrs[i | 1]);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createError(text: string, token: any) {
|
|
|
|
return new Error(`Renderer: ${text} [${stringify(token)}]`);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2017-12-11 16:30:46 +01:00
|
|
|
* Locates the host native element, used for bootstrapping existing nodes into rendering pipeline.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
|
|
|
* @param elementOrSelector Render element or CSS selector to locate the element.
|
|
|
|
*/
|
2017-12-11 16:30:46 +01:00
|
|
|
export function locateHostElement(
|
|
|
|
factory: RendererFactory3, elementOrSelector: RElement | string): RElement|null {
|
2017-12-08 11:48:54 -08:00
|
|
|
ngDevMode && assertDataInRange(-1);
|
2017-12-11 16:30:46 +01:00
|
|
|
rendererFactory = factory;
|
|
|
|
const defaultRenderer = factory.createRenderer(null, null);
|
2017-12-01 14:23:03 -08:00
|
|
|
const rNode = typeof elementOrSelector === 'string' ?
|
2017-12-11 16:30:46 +01:00
|
|
|
((defaultRenderer as ProceduralRenderer3).selectRootElement ?
|
|
|
|
(defaultRenderer as ProceduralRenderer3).selectRootElement(elementOrSelector) :
|
|
|
|
(defaultRenderer as ObjectOrientedRenderer3).querySelector !(elementOrSelector)) :
|
2017-12-01 14:23:03 -08:00
|
|
|
elementOrSelector;
|
|
|
|
if (ngDevMode && !rNode) {
|
|
|
|
if (typeof elementOrSelector === 'string') {
|
|
|
|
throw createError('Host node with selector not found:', elementOrSelector);
|
|
|
|
} else {
|
|
|
|
throw createError('Host node is required:', elementOrSelector);
|
|
|
|
}
|
|
|
|
}
|
2017-12-11 16:30:46 +01:00
|
|
|
return rNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-01-10 18:19:16 -08:00
|
|
|
* Creates the host LNode.
|
2017-12-11 16:30:46 +01:00
|
|
|
*
|
|
|
|
* @param rNode Render host element.
|
2018-01-10 18:19:16 -08:00
|
|
|
* @param def ComponentDef
|
2017-12-11 16:30:46 +01:00
|
|
|
*/
|
|
|
|
export function hostElement(rNode: RElement | null, def: ComponentDef<any>) {
|
2018-01-03 10:45:09 +01:00
|
|
|
resetApplicationState();
|
2017-12-14 15:03:46 -08:00
|
|
|
createLNode(
|
2018-01-10 18:19:16 -08:00
|
|
|
0, LNodeFlags.Element, rNode, createLView(-1, renderer, getOrCreateTView(def.template)));
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds an event listener to the current node.
|
|
|
|
*
|
|
|
|
* If an output exists on one of the node's directives, it also subscribes to the output
|
|
|
|
* and saves the subscription for later cleanup.
|
|
|
|
*
|
|
|
|
* @param eventName Name of the event
|
|
|
|
* @param listener The function to be called when event emits
|
|
|
|
* @param useCapture Whether or not to use capture in event listener.
|
|
|
|
*/
|
2017-12-14 15:03:46 -08:00
|
|
|
export function listener(eventName: string, listener: EventListener, useCapture = false): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
ngDevMode && assertPreviousIsParent();
|
|
|
|
const node = previousOrParentNode;
|
|
|
|
const native = node.native as RElement;
|
|
|
|
|
2017-12-14 15:50:01 -08:00
|
|
|
// In order to match current behavior, native DOM event listeners must be added for all
|
|
|
|
// events (including outputs).
|
|
|
|
if ((renderer as ProceduralRenderer3).listen) {
|
|
|
|
const cleanupFn = (renderer as ProceduralRenderer3).listen(native, eventName, listener);
|
2017-12-01 14:23:03 -08:00
|
|
|
(cleanup || (cleanup = currentView.cleanup = [])).push(cleanupFn, null);
|
|
|
|
} else {
|
|
|
|
native.addEventListener(eventName, listener, useCapture);
|
|
|
|
(cleanup || (cleanup = currentView.cleanup = [])).push(eventName, native, listener, useCapture);
|
|
|
|
}
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
let tNode: TNode|null = node.tNode !;
|
|
|
|
if (tNode.outputs === undefined) {
|
|
|
|
// if we create TNode here, inputs must be undefined so we know they still need to be
|
2017-12-01 14:23:03 -08:00
|
|
|
// checked
|
2018-01-08 20:17:13 -08:00
|
|
|
tNode.outputs = null;
|
|
|
|
tNode = generatePropertyAliases(node.flags, tNode);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
const outputs = tNode.outputs;
|
2017-12-01 14:23:03 -08:00
|
|
|
let outputData: (number | string)[]|undefined;
|
|
|
|
if (outputs && (outputData = outputs[eventName])) {
|
2017-12-14 16:26:28 -08:00
|
|
|
createOutput(outputData, listener);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterates through the outputs associated with a particular event name and subscribes to
|
|
|
|
* each output.
|
|
|
|
*/
|
2017-12-14 16:26:28 -08:00
|
|
|
function createOutput(outputs: (number | string)[], listener: Function): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
for (let i = 0; i < outputs.length; i += 2) {
|
2017-12-11 14:08:52 -08:00
|
|
|
ngDevMode && assertDataInRange(outputs[i] as number);
|
2017-12-14 15:03:46 -08:00
|
|
|
const subscription = data[outputs[i] as number][outputs[i | 1]].subscribe(listener);
|
2017-12-01 14:23:03 -08:00
|
|
|
cleanup !.push(subscription.unsubscribe, subscription);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-14 16:26:28 -08:00
|
|
|
/** Mark the end of the element. */
|
2017-12-01 14:23:03 -08:00
|
|
|
export function elementEnd() {
|
|
|
|
if (isParent) {
|
|
|
|
isParent = false;
|
|
|
|
} else {
|
|
|
|
ngDevMode && assertHasParent();
|
|
|
|
previousOrParentNode = previousOrParentNode.parent !;
|
|
|
|
}
|
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Element);
|
|
|
|
const query = previousOrParentNode.query;
|
2017-12-13 19:34:46 -08:00
|
|
|
query && query.addNode(previousOrParentNode);
|
2018-01-22 17:43:52 -08:00
|
|
|
queueLifecycleHooks(previousOrParentNode.flags, currentView);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update an attribute on an Element. This is used with a `bind` instruction.
|
|
|
|
*
|
2017-12-08 11:48:54 -08:00
|
|
|
* @param index The index of the element to update in the data array
|
2017-12-01 14:23:03 -08:00
|
|
|
* @param attrName Name of attribute. Because it is going to DOM, this is not subject to
|
|
|
|
* renaming as port of minification.
|
|
|
|
* @param value Value to write. This value will go through stringification.
|
|
|
|
*/
|
|
|
|
export function elementAttribute(index: number, attrName: string, value: any): void {
|
|
|
|
if (value !== NO_CHANGE) {
|
2018-01-08 20:17:13 -08:00
|
|
|
const element = data[index] as LElementNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
if (value == null) {
|
2017-12-14 15:50:01 -08:00
|
|
|
(renderer as ProceduralRenderer3).removeAttribute ?
|
|
|
|
(renderer as ProceduralRenderer3).removeAttribute(element.native, attrName) :
|
|
|
|
element.native.removeAttribute(attrName);
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2017-12-14 15:50:01 -08:00
|
|
|
(renderer as ProceduralRenderer3).setAttribute ?
|
2018-01-03 14:05:33 +01:00
|
|
|
(renderer as ProceduralRenderer3)
|
|
|
|
.setAttribute(element.native, attrName, stringify(value)) :
|
|
|
|
element.native.setAttribute(attrName, stringify(value));
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update a property on an Element.
|
|
|
|
*
|
|
|
|
* If the property name also exists as an input property on one of the element's directives,
|
2017-12-14 16:26:28 -08:00
|
|
|
* the component property will be set instead of the element property. This check must
|
|
|
|
* be conducted at runtime so child components that add new @Inputs don't have to be re-compiled.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2017-12-08 11:48:54 -08:00
|
|
|
* @param index The index of the element to update in the data array
|
2017-12-01 14:23:03 -08:00
|
|
|
* @param propName Name of property. Because it is going to DOM, this is not subject to
|
|
|
|
* renaming as part of minification.
|
|
|
|
* @param value New value to write.
|
|
|
|
*/
|
|
|
|
|
|
|
|
export function elementProperty<T>(index: number, propName: string, value: T | NO_CHANGE): void {
|
|
|
|
if (value === NO_CHANGE) return;
|
2018-01-08 20:17:13 -08:00
|
|
|
const node = data[index] as LElementNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
let tNode: TNode|null = node.tNode !;
|
|
|
|
// if tNode.inputs is undefined, a listener has created outputs, but inputs haven't
|
2017-12-11 14:08:52 -08:00
|
|
|
// yet been checked
|
2018-01-08 20:17:13 -08:00
|
|
|
if (tNode.inputs === undefined) {
|
2017-12-01 14:23:03 -08:00
|
|
|
// mark inputs as checked
|
2018-01-08 20:17:13 -08:00
|
|
|
tNode.inputs = null;
|
|
|
|
tNode = generatePropertyAliases(node.flags, tNode, true);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
const inputData = tNode.inputs;
|
2017-12-13 19:34:46 -08:00
|
|
|
let dataValue: PropertyAliasValue|null;
|
2017-12-01 14:23:03 -08:00
|
|
|
if (inputData && (dataValue = inputData[propName])) {
|
|
|
|
setInputsForProperty(dataValue, value);
|
|
|
|
} else {
|
|
|
|
const native = node.native;
|
2017-12-14 15:50:01 -08:00
|
|
|
(renderer as ProceduralRenderer3).setProperty ?
|
|
|
|
(renderer as ProceduralRenderer3).setProperty(native, propName, value) :
|
2017-12-01 14:23:03 -08:00
|
|
|
native.setProperty ? native.setProperty(propName, value) :
|
|
|
|
(native as any)[propName] = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-14 15:50:01 -08:00
|
|
|
/**
|
2018-01-08 20:17:13 -08:00
|
|
|
* Constructs a TNode object from the arguments.
|
2017-12-14 15:50:01 -08:00
|
|
|
*
|
|
|
|
* @param tagName
|
|
|
|
* @param attrs
|
2018-01-10 18:19:16 -08:00
|
|
|
* @param data
|
2018-01-08 20:17:13 -08:00
|
|
|
* @returns the TNode object
|
2017-12-14 15:50:01 -08:00
|
|
|
*/
|
2018-01-08 20:17:13 -08:00
|
|
|
function createTNode(
|
2018-01-10 18:19:16 -08:00
|
|
|
tagName: string | null, attrs: string[] | null, data: TContainer | null,
|
2018-01-08 20:17:13 -08:00
|
|
|
localName: string | null): TNode {
|
2017-12-08 11:48:54 -08:00
|
|
|
return {
|
2017-12-12 14:42:28 +01:00
|
|
|
tagName: tagName,
|
|
|
|
attrs: attrs,
|
2017-12-19 16:51:42 +01:00
|
|
|
localNames: localName ? [localName, -1] : null,
|
2017-12-08 11:48:54 -08:00
|
|
|
initialInputs: undefined,
|
|
|
|
inputs: undefined,
|
2017-12-11 14:08:52 -08:00
|
|
|
outputs: undefined,
|
2018-01-10 18:19:16 -08:00
|
|
|
data: data
|
2017-12-08 11:48:54 -08:00
|
|
|
};
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a list of directive indices and minified input names, sets the
|
|
|
|
* input properties on the corresponding directives.
|
|
|
|
*/
|
|
|
|
function setInputsForProperty(inputs: (number | string)[], value: any): void {
|
|
|
|
for (let i = 0; i < inputs.length; i += 2) {
|
2017-12-11 14:08:52 -08:00
|
|
|
ngDevMode && assertDataInRange(inputs[i] as number);
|
|
|
|
data[inputs[i] as number][inputs[i | 1]] = value;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function consolidates all the inputs or outputs defined by directives
|
2018-01-10 18:19:16 -08:00
|
|
|
* on this node into one object and stores it in tData so it can
|
2017-12-01 14:23:03 -08:00
|
|
|
* be shared between all templates of this type.
|
|
|
|
*
|
2018-01-10 18:19:16 -08:00
|
|
|
* @param index Index where data should be stored in tData
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-01-08 20:17:13 -08:00
|
|
|
function generatePropertyAliases(flags: number, tNode: TNode, isInputData = false): TNode {
|
2017-12-01 14:23:03 -08:00
|
|
|
const start = flags >> LNodeFlags.INDX_SHIFT;
|
|
|
|
const size = (flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT;
|
|
|
|
|
|
|
|
for (let i = start, ii = start + size; i < ii; i++) {
|
2018-01-10 18:19:16 -08:00
|
|
|
const directiveDef: DirectiveDef<any> = tData ![i] as DirectiveDef<any>;
|
2017-12-13 19:34:46 -08:00
|
|
|
const propertyAliasMap: {[publicName: string]: string} =
|
2017-12-01 14:23:03 -08:00
|
|
|
isInputData ? directiveDef.inputs : directiveDef.outputs;
|
2017-12-13 19:34:46 -08:00
|
|
|
for (let publicName in propertyAliasMap) {
|
|
|
|
if (propertyAliasMap.hasOwnProperty(publicName)) {
|
|
|
|
const internalName = propertyAliasMap[publicName];
|
2018-01-08 20:17:13 -08:00
|
|
|
const staticDirData: PropertyAliases = isInputData ?
|
|
|
|
(tNode.inputs || (tNode.inputs = {})) :
|
|
|
|
(tNode.outputs || (tNode.outputs = {}));
|
2017-12-13 19:34:46 -08:00
|
|
|
const hasProperty: boolean = staticDirData.hasOwnProperty(publicName);
|
|
|
|
hasProperty ? staticDirData[publicName].push(i, internalName) :
|
|
|
|
(staticDirData[publicName] = [i, internalName]);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-01-08 20:17:13 -08:00
|
|
|
return tNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add or remove a class in a classList.
|
|
|
|
*
|
|
|
|
* This instruction is meant to handle the [class.foo]="exp" case
|
|
|
|
*
|
2017-12-08 11:48:54 -08:00
|
|
|
* @param index The index of the element to update in the data array
|
2017-12-01 14:23:03 -08:00
|
|
|
* @param className Name of class to toggle. Because it is going to DOM, this is not subject to
|
|
|
|
* renaming as part of minification.
|
|
|
|
* @param value A value indicating if a given class should be added or removed.
|
|
|
|
*/
|
|
|
|
export function elementClass<T>(index: number, className: string, value: T | NO_CHANGE): void {
|
|
|
|
if (value !== NO_CHANGE) {
|
2018-01-08 20:17:13 -08:00
|
|
|
const lElement = data[index] as LElementNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
if (value) {
|
2017-12-14 15:50:01 -08:00
|
|
|
(renderer as ProceduralRenderer3).addClass ?
|
|
|
|
(renderer as ProceduralRenderer3).addClass(lElement.native, className) :
|
2017-12-01 14:23:03 -08:00
|
|
|
lElement.native.classList.add(className);
|
|
|
|
|
|
|
|
} else {
|
2017-12-14 15:50:01 -08:00
|
|
|
(renderer as ProceduralRenderer3).removeClass ?
|
|
|
|
(renderer as ProceduralRenderer3).removeClass(lElement.native, className) :
|
2017-12-01 14:23:03 -08:00
|
|
|
lElement.native.classList.remove(className);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update a given style on an Element.
|
|
|
|
*
|
2017-12-08 11:48:54 -08:00
|
|
|
* @param index Index of the element to change in the data array
|
2017-12-01 14:23:03 -08:00
|
|
|
* @param styleName Name of property. Because it is going to DOM this is not subject to
|
|
|
|
* renaming as part of minification.
|
|
|
|
* @param value New value to write (null to remove).
|
|
|
|
* @param suffix Suffix to add to style's value (optional).
|
|
|
|
*/
|
|
|
|
export function elementStyle<T>(
|
|
|
|
index: number, styleName: string, value: T | NO_CHANGE, suffix?: string): void {
|
|
|
|
if (value !== NO_CHANGE) {
|
2018-01-08 20:17:13 -08:00
|
|
|
const lElement = data[index] as LElementNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
if (value == null) {
|
2017-12-14 15:50:01 -08:00
|
|
|
(renderer as ProceduralRenderer3).removeStyle ?
|
|
|
|
(renderer as ProceduralRenderer3)
|
2017-12-01 14:23:03 -08:00
|
|
|
.removeStyle(lElement.native, styleName, RendererStyleFlags3.DashCase) :
|
|
|
|
lElement.native.style.removeProperty(styleName);
|
|
|
|
} else {
|
2017-12-14 15:50:01 -08:00
|
|
|
(renderer as ProceduralRenderer3).setStyle ?
|
|
|
|
(renderer as ProceduralRenderer3)
|
2017-12-01 14:23:03 -08:00
|
|
|
.setStyle(
|
|
|
|
lElement.native, styleName, suffix ? stringify(value) + suffix : stringify(value),
|
|
|
|
RendererStyleFlags3.DashCase) :
|
|
|
|
lElement.native.style.setProperty(
|
|
|
|
styleName, suffix ? stringify(value) + suffix : stringify(value));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////
|
2017-12-14 15:50:01 -08:00
|
|
|
//// Text
|
2017-12-01 14:23:03 -08:00
|
|
|
//////////////////////////
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create static text node
|
|
|
|
*
|
2017-12-08 11:48:54 -08:00
|
|
|
* @param index Index of the node in the data array.
|
2017-12-01 14:23:03 -08:00
|
|
|
* @param value Value to write. This value will be stringified.
|
|
|
|
* If value is not provided than the actual creation of the text node is delayed.
|
|
|
|
*/
|
2017-12-14 16:26:28 -08:00
|
|
|
export function text(index: number, value?: any): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
ngDevMode && assertEqual(currentView.bindingStartIndex, null, 'bindingStartIndex');
|
|
|
|
const textNode = value != null ?
|
2017-12-14 15:50:01 -08:00
|
|
|
((renderer as ProceduralRenderer3).createText ?
|
|
|
|
(renderer as ProceduralRenderer3).createText(stringify(value)) :
|
|
|
|
(renderer as ObjectOrientedRenderer3).createTextNode !(stringify(value))) :
|
2017-12-01 14:23:03 -08:00
|
|
|
null;
|
2017-12-08 11:48:54 -08:00
|
|
|
const node = createLNode(index, LNodeFlags.Element, textNode);
|
2017-12-01 14:23:03 -08:00
|
|
|
// Text nodes are self closing.
|
|
|
|
isParent = false;
|
|
|
|
appendChild(node.parent !, textNode, currentView);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create text node with binding
|
|
|
|
* Bindings should be handled externally with the proper bind(1-8) method
|
|
|
|
*
|
2017-12-08 11:48:54 -08:00
|
|
|
* @param index Index of the node in the data array.
|
2017-12-01 14:23:03 -08:00
|
|
|
* @param value Stringified value to write.
|
|
|
|
*/
|
2017-12-14 16:26:28 -08:00
|
|
|
export function textBinding<T>(index: number, value: T | NO_CHANGE): void {
|
2018-01-23 18:39:09 -08:00
|
|
|
ngDevMode && assertDataInRange(index);
|
|
|
|
let existingNode = data[index] as LTextNode;
|
|
|
|
ngDevMode && assertNotNull(existingNode, 'existing node');
|
|
|
|
if (existingNode.native) {
|
2017-12-01 14:23:03 -08:00
|
|
|
// If DOM node exists and value changed, update textContent
|
|
|
|
value !== NO_CHANGE &&
|
2017-12-14 15:50:01 -08:00
|
|
|
((renderer as ProceduralRenderer3).setValue ?
|
|
|
|
(renderer as ProceduralRenderer3).setValue(existingNode.native, stringify(value)) :
|
2017-12-01 14:23:03 -08:00
|
|
|
existingNode.native.textContent = stringify(value));
|
2018-01-23 18:39:09 -08:00
|
|
|
} else {
|
2017-12-01 14:23:03 -08:00
|
|
|
// Node was created but DOM node creation was delayed. Create and append now.
|
|
|
|
existingNode.native =
|
2017-12-14 15:50:01 -08:00
|
|
|
((renderer as ProceduralRenderer3).createText ?
|
|
|
|
(renderer as ProceduralRenderer3).createText(stringify(value)) :
|
|
|
|
(renderer as ObjectOrientedRenderer3).createTextNode !(stringify(value)));
|
2017-12-01 14:23:03 -08:00
|
|
|
insertChild(existingNode, currentView);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////
|
|
|
|
//// Directive
|
|
|
|
//////////////////////////
|
|
|
|
|
2018-01-08 21:57:50 -08:00
|
|
|
/**
|
|
|
|
* Create a directive.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
|
|
|
* NOTE: directives can be created in order other than the index order. They can also
|
|
|
|
* be retrieved before they are created in which case the value will be null.
|
|
|
|
*
|
|
|
|
* @param index Each directive in a `View` will have a unique index. Directives can
|
|
|
|
* be created or retrieved out of order.
|
|
|
|
* @param directive The directive instance.
|
|
|
|
* @param directiveDef DirectiveDef object which contains information about the template.
|
2018-01-08 21:57:50 -08:00
|
|
|
* @param queryName Name under which the query can retrieve the directive instance.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-01-08 21:57:50 -08:00
|
|
|
export function directiveCreate<T>(
|
|
|
|
index: number, directive: T, directiveDef: DirectiveDef<T>, queryName?: string | null): T {
|
2017-12-01 14:23:03 -08:00
|
|
|
let instance;
|
2018-01-08 21:57:50 -08:00
|
|
|
ngDevMode && assertEqual(currentView.bindingStartIndex, null, 'bindingStartIndex');
|
|
|
|
ngDevMode && assertPreviousIsParent();
|
|
|
|
let flags = previousOrParentNode !.flags;
|
|
|
|
let size = flags & LNodeFlags.SIZE_MASK;
|
|
|
|
if (size === 0) {
|
|
|
|
flags = (index << LNodeFlags.INDX_SHIFT) | LNodeFlags.SIZE_SKIP | flags & LNodeFlags.TYPE_MASK;
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2018-01-08 21:57:50 -08:00
|
|
|
flags += LNodeFlags.SIZE_SKIP;
|
|
|
|
}
|
|
|
|
previousOrParentNode !.flags = flags;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-08 21:57:50 -08:00
|
|
|
ngDevMode && assertDataInRange(index - 1);
|
|
|
|
Object.defineProperty(
|
|
|
|
directive, NG_HOST_SYMBOL, {enumerable: false, value: previousOrParentNode});
|
2017-12-19 16:51:42 +01:00
|
|
|
|
2018-01-08 21:57:50 -08:00
|
|
|
data[index] = instance = directive;
|
2017-12-11 14:08:52 -08:00
|
|
|
|
2018-01-10 18:19:16 -08:00
|
|
|
if (index >= tData.length) {
|
|
|
|
tData[index] = directiveDef !;
|
2018-01-08 21:57:50 -08:00
|
|
|
if (queryName) {
|
2018-01-10 18:19:16 -08:00
|
|
|
ngDevMode && assertNotNull(previousOrParentNode.tNode, 'previousOrParentNode.tNode');
|
|
|
|
const tNode = previousOrParentNode !.tNode !;
|
|
|
|
(tNode.localNames || (tNode.localNames = [])).push(queryName, index);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-01-08 21:57:50 -08:00
|
|
|
}
|
2017-12-11 14:08:52 -08:00
|
|
|
|
2018-01-08 21:57:50 -08:00
|
|
|
const diPublic = directiveDef !.diPublic;
|
|
|
|
if (diPublic) {
|
|
|
|
diPublic(directiveDef !);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2017-12-11 14:08:52 -08:00
|
|
|
|
2018-01-10 18:19:16 -08:00
|
|
|
const tNode: TNode|null = previousOrParentNode.tNode !;
|
|
|
|
if (tNode && tNode.attrs) {
|
|
|
|
setInputsFromAttrs<T>(instance, directiveDef !.inputs, tNode);
|
2018-01-08 21:57:50 -08:00
|
|
|
}
|
2018-01-22 17:43:52 -08:00
|
|
|
|
|
|
|
// Init hooks are queued now so ngOnInit is called in host components before
|
|
|
|
// any projected components.
|
2018-01-23 18:39:09 -08:00
|
|
|
queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, currentView.tView);
|
2018-01-22 17:43:52 -08:00
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets initial input properties on directive instances from attribute data
|
|
|
|
*
|
|
|
|
* @param instance Instance of the directive on which to set the initial inputs
|
|
|
|
* @param inputs The list of inputs from the directive def
|
2018-01-08 20:17:13 -08:00
|
|
|
* @param tNode The static data for this node
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-01-08 20:17:13 -08:00
|
|
|
function setInputsFromAttrs<T>(instance: T, inputs: {[key: string]: string}, tNode: TNode): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
const directiveIndex =
|
|
|
|
((previousOrParentNode.flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT) - 1;
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
let initialInputData = tNode.initialInputs as InitialInputData | undefined;
|
2017-12-01 14:23:03 -08:00
|
|
|
if (initialInputData === undefined || directiveIndex >= initialInputData.length) {
|
2018-01-08 20:17:13 -08:00
|
|
|
initialInputData = generateInitialInputs(directiveIndex, inputs, tNode);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
const initialInputs: InitialInputs|null = initialInputData[directiveIndex];
|
|
|
|
if (initialInputs) {
|
|
|
|
for (let i = 0; i < initialInputs.length; i += 2) {
|
|
|
|
(instance as any)[initialInputs[i]] = initialInputs[i | 1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-12-14 15:50:01 -08:00
|
|
|
* Generates initialInputData for a node and stores it in the template's static storage
|
|
|
|
* so subsequent template invocations don't have to recalculate it.
|
|
|
|
*
|
|
|
|
* initialInputData is an array containing values that need to be set as input properties
|
|
|
|
* for directives on this node, but only once on creation. We need this array to support
|
|
|
|
* the case where you set an @Input property of a directive using attribute-like syntax.
|
|
|
|
* e.g. if you have a `name` @Input, you can set it once like this:
|
|
|
|
*
|
|
|
|
* <my-component name="Bess"></my-component>
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
|
|
|
* @param directiveIndex Index to store the initial input data
|
|
|
|
* @param inputs The list of inputs from the directive def
|
2018-01-08 20:17:13 -08:00
|
|
|
* @param tNode The static data on this node
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
|
|
|
function generateInitialInputs(
|
2018-01-08 20:17:13 -08:00
|
|
|
directiveIndex: number, inputs: {[key: string]: string}, tNode: TNode): InitialInputData {
|
|
|
|
const initialInputData: InitialInputData = tNode.initialInputs || (tNode.initialInputs = []);
|
2017-12-01 14:23:03 -08:00
|
|
|
initialInputData[directiveIndex] = null;
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
const attrs = tNode.attrs !;
|
2017-12-01 14:23:03 -08:00
|
|
|
for (let i = 0; i < attrs.length; i += 2) {
|
|
|
|
const attrName = attrs[i];
|
|
|
|
const minifiedInputName = inputs[attrName];
|
|
|
|
if (minifiedInputName !== undefined) {
|
|
|
|
const inputsToStore: InitialInputs =
|
|
|
|
initialInputData[directiveIndex] || (initialInputData[directiveIndex] = []);
|
|
|
|
inputsToStore.push(minifiedInputName, attrs[i | 1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return initialInputData;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////
|
|
|
|
//// ViewContainer & View
|
|
|
|
//////////////////////////
|
|
|
|
|
|
|
|
/**
|
2018-01-08 20:17:13 -08:00
|
|
|
* Creates an LContainerNode.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-01-08 20:17:13 -08:00
|
|
|
* Only `LViewNodes` can go into `LContainerNodes`.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2017-12-08 11:48:54 -08:00
|
|
|
* @param index The index of the container in the data array
|
2017-12-01 14:23:03 -08:00
|
|
|
* @param template Optional inline template
|
2017-12-14 15:50:01 -08:00
|
|
|
* @param tagName The name of the container element, if applicable
|
|
|
|
* @param attrs The attrs attached to the container, if applicable
|
2018-01-08 21:57:50 -08:00
|
|
|
* @param localRefs A set of local reference bindings on the element.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-01-09 13:32:24 -08:00
|
|
|
export function container(
|
2018-01-08 21:57:50 -08:00
|
|
|
index: number, directiveTypes?: DirectiveType<any>[], template?: ComponentTemplate<any>,
|
|
|
|
tagName?: string, attrs?: string[], localRefs?: string[] | null): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
ngDevMode && assertEqual(currentView.bindingStartIndex, null, 'bindingStartIndex');
|
|
|
|
|
2017-12-13 19:34:46 -08:00
|
|
|
// If the direct parent of the container is a view, its views (including its comment)
|
2017-12-01 14:23:03 -08:00
|
|
|
// will need to be added through insertView() when its parent view is being inserted.
|
2017-12-13 19:34:46 -08:00
|
|
|
// For now, it is marked "headless" so we know to append its views later.
|
2018-01-08 20:17:13 -08:00
|
|
|
let renderParent: LElementNode|null = null;
|
2017-12-01 14:23:03 -08:00
|
|
|
const currentParent = isParent ? previousOrParentNode : previousOrParentNode.parent !;
|
|
|
|
ngDevMode && assertNotEqual(currentParent, null, 'currentParent');
|
2018-01-25 15:32:21 +01:00
|
|
|
|
|
|
|
if ((currentParent.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element &&
|
|
|
|
(currentParent.view !==
|
|
|
|
currentView /* Crossing View Boundaries, it is Component, but add Element of View */
|
|
|
|
|| currentParent.data === null /* Regular Element. */)) {
|
2017-12-01 14:23:03 -08:00
|
|
|
// we are adding to an Element which is either:
|
|
|
|
// - Not a component (will not be re-projected, just added)
|
|
|
|
// - View of the Component
|
2018-01-08 20:17:13 -08:00
|
|
|
renderParent = currentParent as LElementNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-01-17 17:55:55 +01:00
|
|
|
const lContainer = <LContainer>{
|
2017-12-13 19:34:46 -08:00
|
|
|
views: [],
|
2017-12-01 14:23:03 -08:00
|
|
|
nextIndex: 0, renderParent,
|
|
|
|
template: template == null ? null : template,
|
|
|
|
next: null,
|
2018-01-17 10:09:05 -08:00
|
|
|
parent: currentView,
|
|
|
|
dynamicViewCount: 0,
|
2018-01-25 15:32:21 +01:00
|
|
|
query: null,
|
|
|
|
nextNative: undefined
|
2018-01-17 17:55:55 +01:00
|
|
|
};
|
|
|
|
|
2018-01-25 15:32:21 +01:00
|
|
|
const node = createLNode(index, LNodeFlags.Container, null, lContainer);
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
if (node.tNode == null) {
|
2018-01-08 21:57:50 -08:00
|
|
|
// TODO(misko): implement queryName caching
|
|
|
|
const queryName: string|null = hack_findQueryName(null, localRefs, '');
|
2018-01-10 18:19:16 -08:00
|
|
|
node.tNode = tData[index] = createTNode(tagName || null, attrs || null, [], queryName || null);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Containers are added to the current view tree instead of their embedded views
|
|
|
|
// because views can be removed and re-inserted.
|
|
|
|
addToViewTree(node.data);
|
2018-01-08 21:57:50 -08:00
|
|
|
hack_declareDirectives(index, directiveTypes, localRefs);
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-09 13:32:24 -08:00
|
|
|
isParent = false;
|
2017-12-01 14:23:03 -08:00
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container);
|
2018-01-17 17:55:55 +01:00
|
|
|
const query = node.query;
|
|
|
|
if (query) {
|
|
|
|
// check if a given container node matches
|
|
|
|
query.addNode(node);
|
|
|
|
// prepare place for matching nodes from views inserted into a given container
|
|
|
|
lContainer.query = query.container();
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets a container up to receive views.
|
|
|
|
*
|
2017-12-08 11:48:54 -08:00
|
|
|
* @param index The index of the container in the data array
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2017-12-14 16:26:28 -08:00
|
|
|
export function containerRefreshStart(index: number): void {
|
2017-12-08 11:48:54 -08:00
|
|
|
ngDevMode && assertDataInRange(index);
|
|
|
|
previousOrParentNode = data[index] as LNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container);
|
|
|
|
isParent = true;
|
2018-01-25 15:32:21 +01:00
|
|
|
const container = previousOrParentNode as LContainerNode;
|
|
|
|
container.data.nextIndex = 0;
|
|
|
|
ngDevMode &&
|
|
|
|
assertEqual(
|
|
|
|
container.data.nextNative === undefined, true, 'container.data.nextNative === undefined');
|
2018-01-22 17:43:52 -08:00
|
|
|
|
|
|
|
// We need to execute init hooks here so ngOnInit hooks are called in top level views
|
|
|
|
// before they are called in embedded views (for backwards compatibility).
|
|
|
|
executeInitHooks(currentView);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-01-08 20:17:13 -08:00
|
|
|
* Marks the end of the LContainerNode.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-01-08 20:17:13 -08:00
|
|
|
* Marking the end of LContainerNode is the time when to child Views get inserted or removed.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2017-12-14 16:26:28 -08:00
|
|
|
export function containerRefreshEnd(): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
if (isParent) {
|
|
|
|
isParent = false;
|
|
|
|
} else {
|
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.View);
|
|
|
|
ngDevMode && assertHasParent();
|
|
|
|
previousOrParentNode = previousOrParentNode.parent !;
|
|
|
|
}
|
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container);
|
2018-01-08 20:17:13 -08:00
|
|
|
const container = previousOrParentNode as LContainerNode;
|
2018-01-25 15:32:21 +01:00
|
|
|
container.data.nextNative = undefined;
|
2017-12-01 14:23:03 -08:00
|
|
|
ngDevMode && assertNodeType(container, LNodeFlags.Container);
|
|
|
|
const nextIndex = container.data.nextIndex;
|
2017-12-13 19:34:46 -08:00
|
|
|
while (nextIndex < container.data.views.length) {
|
2017-12-01 14:23:03 -08:00
|
|
|
// remove extra view.
|
|
|
|
removeView(container, nextIndex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-17 10:09:05 -08:00
|
|
|
function refreshDynamicChildren() {
|
|
|
|
for (let current = currentView.child; current !== null; current = current.next) {
|
|
|
|
if (current.dynamicViewCount !== 0 && (current as LContainer).views) {
|
|
|
|
const container = current as LContainer;
|
|
|
|
for (let i = 0; i < container.views.length; i++) {
|
|
|
|
const view = container.views[i];
|
|
|
|
renderEmbeddedTemplate(view, view.data.template !, view.data.context !, renderer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
2018-01-08 20:17:13 -08:00
|
|
|
* Creates an LViewNode.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
|
|
|
* @param viewBlockId The ID of this view
|
|
|
|
* @return Whether or not this view is in creation mode
|
|
|
|
*/
|
2017-12-14 16:26:28 -08:00
|
|
|
export function viewStart(viewBlockId: number): boolean {
|
2018-01-08 20:17:13 -08:00
|
|
|
const container =
|
|
|
|
(isParent ? previousOrParentNode : previousOrParentNode.parent !) as LContainerNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
ngDevMode && assertNodeType(container, LNodeFlags.Container);
|
2018-01-08 20:17:13 -08:00
|
|
|
const lContainer = container.data;
|
|
|
|
const views = lContainer.views;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
const existingView: LViewNode|false =
|
|
|
|
!creationMode && lContainer.nextIndex < views.length && views[lContainer.nextIndex];
|
|
|
|
let viewUpdateMode = existingView && viewBlockId === (existingView as LViewNode).data.id;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
if (viewUpdateMode) {
|
2018-01-08 20:17:13 -08:00
|
|
|
previousOrParentNode = views[lContainer.nextIndex++];
|
2017-12-01 14:23:03 -08:00
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.View);
|
|
|
|
isParent = true;
|
2018-01-08 20:17:13 -08:00
|
|
|
enterView((existingView as LViewNode).data, previousOrParentNode as LViewNode);
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2018-01-08 20:17:13 -08:00
|
|
|
// When we create a new LView, we always reset the state of the instructions.
|
2018-01-10 18:19:16 -08:00
|
|
|
const newView =
|
|
|
|
createLView(viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container));
|
2018-01-17 17:55:55 +01:00
|
|
|
if (lContainer.query) {
|
|
|
|
newView.query = lContainer.query.enterView(lContainer.nextIndex);
|
|
|
|
}
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
enterView(newView, createLNode(null, LNodeFlags.View, null, newView));
|
|
|
|
lContainer.nextIndex++;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2017-12-08 11:48:54 -08:00
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
return !viewUpdateMode;
|
|
|
|
}
|
|
|
|
|
2017-12-08 11:48:54 -08:00
|
|
|
/**
|
2018-01-10 18:19:16 -08:00
|
|
|
* Initialize the TView (e.g. static data) for the active embedded view.
|
2017-12-11 14:08:52 -08:00
|
|
|
*
|
2018-01-10 18:19:16 -08:00
|
|
|
* Each embedded view needs to set the global tData variable to the static data for
|
2017-12-11 14:08:52 -08:00
|
|
|
* that view. Otherwise, the view's static data for a particular node would overwrite
|
2018-01-10 18:19:16 -08:00
|
|
|
* the static data for a node in the view above it with the same index (since it's in the
|
2017-12-11 14:08:52 -08:00
|
|
|
* same template).
|
2017-12-08 11:48:54 -08:00
|
|
|
*
|
2018-01-10 18:19:16 -08:00
|
|
|
* @param viewIndex The index of the TView in TContainer
|
2017-12-11 14:08:52 -08:00
|
|
|
* @param parent The parent container in which to look for the view's static data
|
2018-01-10 18:19:16 -08:00
|
|
|
* @returns TView
|
2017-12-08 11:48:54 -08:00
|
|
|
*/
|
2018-01-10 18:19:16 -08:00
|
|
|
function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TView {
|
2017-12-11 14:08:52 -08:00
|
|
|
ngDevMode && assertNodeType(parent, LNodeFlags.Container);
|
2018-01-10 18:19:16 -08:00
|
|
|
const tContainer = (parent !.tNode as TContainerNode).data;
|
|
|
|
if (viewIndex >= tContainer.length || tContainer[viewIndex] == null) {
|
2018-01-22 17:43:52 -08:00
|
|
|
tContainer[viewIndex] = createTView();
|
2017-12-08 11:48:54 -08:00
|
|
|
}
|
2018-01-10 18:19:16 -08:00
|
|
|
return tContainer[viewIndex];
|
2017-12-08 11:48:54 -08:00
|
|
|
}
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
/** Marks the end of the LViewNode. */
|
2017-12-01 14:23:03 -08:00
|
|
|
export function viewEnd(): void {
|
|
|
|
isParent = false;
|
2018-01-08 20:17:13 -08:00
|
|
|
const viewNode = previousOrParentNode = currentView.node as LViewNode;
|
|
|
|
const container = previousOrParentNode.parent as LContainerNode;
|
2018-01-17 10:09:05 -08:00
|
|
|
if (container) {
|
|
|
|
ngDevMode && assertNodeType(viewNode, LNodeFlags.View);
|
|
|
|
ngDevMode && assertNodeType(container, LNodeFlags.Container);
|
|
|
|
const containerState = container.data;
|
|
|
|
const previousView = containerState.nextIndex <= containerState.views.length ?
|
|
|
|
containerState.views[containerState.nextIndex - 1] as LViewNode :
|
|
|
|
null;
|
|
|
|
const viewIdChanged = previousView == null ? true : previousView.data.id !== viewNode.data.id;
|
|
|
|
|
|
|
|
if (viewIdChanged) {
|
|
|
|
insertView(container, viewNode, containerState.nextIndex - 1);
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
leaveView(currentView !.parent !);
|
|
|
|
ngDevMode && assertEqual(isParent, false, 'isParent');
|
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.View);
|
|
|
|
}
|
2018-01-17 10:09:05 -08:00
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/////////////
|
|
|
|
|
2017-12-14 16:26:28 -08:00
|
|
|
/**
|
|
|
|
* Refreshes the component view.
|
|
|
|
*
|
|
|
|
* In other words, enters the component's view and processes it to update bindings, queries, etc.
|
|
|
|
*
|
|
|
|
* @param directiveIndex
|
|
|
|
* @param elementIndex
|
|
|
|
*/
|
2018-01-22 19:52:06 -08:00
|
|
|
export function componentRefresh<T>(directiveIndex: number, elementIndex: number): void {
|
2018-01-22 17:43:52 -08:00
|
|
|
executeInitHooks(currentView);
|
|
|
|
executeContentHooks(currentView);
|
2018-01-22 19:52:06 -08:00
|
|
|
const template = (tData[directiveIndex] as ComponentDef<T>).template;
|
|
|
|
if (template != null) {
|
|
|
|
ngDevMode && assertDataInRange(elementIndex);
|
|
|
|
const element = data ![elementIndex] as LElementNode;
|
|
|
|
ngDevMode && assertNodeType(element, LNodeFlags.Element);
|
|
|
|
ngDevMode && assertNotEqual(element.data, null, 'isComponent');
|
|
|
|
ngDevMode && assertDataInRange(directiveIndex);
|
|
|
|
const directive = data[directiveIndex];
|
|
|
|
const hostView = element.data !;
|
|
|
|
ngDevMode && assertNotEqual(hostView, null, 'hostView');
|
|
|
|
const oldView = enterView(hostView, element);
|
|
|
|
try {
|
|
|
|
template(directive, creationMode);
|
|
|
|
} finally {
|
|
|
|
refreshDynamicChildren();
|
|
|
|
leaveView(oldView);
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-01-23 10:57:48 -08:00
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Instruction to distribute projectable nodes among <ng-content> occurrences in a given template.
|
|
|
|
* It takes all the selectors from the entire component's template and decides where
|
|
|
|
* each projected node belongs (it re-distributes nodes among "buckets" where each "bucket" is
|
|
|
|
* backed by a selector).
|
|
|
|
*
|
2017-12-19 15:01:05 -08:00
|
|
|
* @param selectors
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-01-18 13:27:01 -08:00
|
|
|
export function projectionDef(index: number, selectors?: CssSelector[]): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
const noOfNodeBuckets = selectors ? selectors.length + 1 : 1;
|
|
|
|
const distributedNodes = new Array<LNode[]>(noOfNodeBuckets);
|
|
|
|
for (let i = 0; i < noOfNodeBuckets; i++) {
|
|
|
|
distributedNodes[i] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
const componentNode = findComponentHost(currentView);
|
|
|
|
let componentChild = componentNode.child;
|
|
|
|
|
|
|
|
while (componentChild !== null) {
|
|
|
|
if (!selectors) {
|
|
|
|
distributedNodes[0].push(componentChild);
|
|
|
|
} else if (
|
|
|
|
(componentChild.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element ||
|
|
|
|
(componentChild.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Container) {
|
|
|
|
// Only trying to match selectors against:
|
|
|
|
// - elements, excluding text nodes;
|
|
|
|
// - containers that have tagName and attributes associated.
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
if (componentChild.tNode) {
|
2017-12-01 14:23:03 -08:00
|
|
|
for (let i = 0; i < selectors !.length; i++) {
|
2018-01-08 20:17:13 -08:00
|
|
|
if (isNodeMatchingSelector(componentChild.tNode, selectors ![i])) {
|
2017-12-01 14:23:03 -08:00
|
|
|
distributedNodes[i + 1].push(componentChild);
|
|
|
|
break; // first matching selector "captures" a given node
|
|
|
|
} else {
|
|
|
|
distributedNodes[0].push(componentChild);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
distributedNodes[0].push(componentChild);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if ((componentChild.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Projection) {
|
2018-01-08 20:17:13 -08:00
|
|
|
// we don't descend into nodes to re-project (not trying to match selectors against nodes to
|
2017-12-01 14:23:03 -08:00
|
|
|
// re-project)
|
|
|
|
distributedNodes[0].push(componentChild);
|
|
|
|
}
|
|
|
|
componentChild = componentChild.next;
|
|
|
|
}
|
|
|
|
|
2018-01-18 13:27:01 -08:00
|
|
|
ngDevMode && assertDataNext(index);
|
|
|
|
data[index] = distributedNodes;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-01-25 15:32:21 +01:00
|
|
|
/**
|
|
|
|
* Updates the linked list of a projection node, by appending another linked list.
|
|
|
|
*
|
|
|
|
* @param projectionNode Projection node whose projected nodes linked list has to be updated
|
|
|
|
* @param appendedFirst First node of the linked list to append.
|
|
|
|
* @param appendedLast Last node of the linked list to append.
|
|
|
|
*/
|
|
|
|
function appendToProjectionNode(
|
|
|
|
projectionNode: LProjectionNode,
|
|
|
|
appendedFirst: LElementNode | LTextNode | LContainerNode | null,
|
|
|
|
appendedLast: LElementNode | LTextNode | LContainerNode | null) {
|
|
|
|
// appendedFirst can be null if and only if appendedLast is also null
|
|
|
|
ngDevMode &&
|
|
|
|
assertEqual(!appendedFirst === !appendedLast, true, '!appendedFirst === !appendedLast');
|
|
|
|
if (!appendedLast) {
|
|
|
|
// nothing to append
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const projectionNodeData = projectionNode.data;
|
|
|
|
if (projectionNodeData.last) {
|
|
|
|
projectionNodeData.last.pNextOrParent = appendedFirst;
|
|
|
|
} else {
|
|
|
|
projectionNodeData.first = appendedFirst;
|
|
|
|
}
|
|
|
|
projectionNodeData.last = appendedLast;
|
|
|
|
appendedLast.pNextOrParent = projectionNode;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
|
|
|
* Inserts previously re-distributed projected nodes. This instruction must be preceded by a call
|
2017-12-14 16:26:28 -08:00
|
|
|
* to the projectionDef instruction.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2017-12-19 15:01:05 -08:00
|
|
|
* @param nodeIndex
|
|
|
|
* @param localIndex - index under which distribution of projected nodes was memorized
|
|
|
|
* @param selectorIndex - 0 means <ng-content> without any selector
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2017-12-14 15:03:46 -08:00
|
|
|
export function projection(nodeIndex: number, localIndex: number, selectorIndex: number = 0): void {
|
2018-01-25 15:32:21 +01:00
|
|
|
const node = createLNode(nodeIndex, LNodeFlags.Projection, null, {first: null, last: null});
|
2017-12-01 14:23:03 -08:00
|
|
|
isParent = false; // self closing
|
|
|
|
const currentParent = node.parent;
|
|
|
|
|
|
|
|
// re-distribution of projectable nodes is memorized on a component's view level
|
|
|
|
const componentNode = findComponentHost(currentView);
|
|
|
|
|
|
|
|
// make sure that nodes to project were memorized
|
|
|
|
const nodesForSelector =
|
2017-12-08 11:48:54 -08:00
|
|
|
valueInData<LNode[][]>(componentNode.data !.data !, localIndex)[selectorIndex];
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-25 15:32:21 +01:00
|
|
|
// build the linked list of projected nodes:
|
2017-12-01 14:23:03 -08:00
|
|
|
for (let i = 0; i < nodesForSelector.length; i++) {
|
|
|
|
const nodeToProject = nodesForSelector[i];
|
|
|
|
if ((nodeToProject.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Projection) {
|
2018-01-25 15:32:21 +01:00
|
|
|
const previouslyProjected = (nodeToProject as LProjectionNode).data;
|
|
|
|
appendToProjectionNode(node, previouslyProjected.first, previouslyProjected.last);
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2018-01-25 15:32:21 +01:00
|
|
|
appendToProjectionNode(
|
|
|
|
node, nodeToProject as LTextNode | LElementNode | LContainerNode,
|
|
|
|
nodeToProject as LTextNode | LElementNode | LContainerNode);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
2018-01-25 15:32:21 +01:00
|
|
|
|
|
|
|
// process each node in the list of projected nodes:
|
|
|
|
let nodeToProject: LNode|null = node.data.first;
|
|
|
|
const lastNodeToProject = node.data.last;
|
|
|
|
while (nodeToProject) {
|
|
|
|
processProjectedNode(
|
|
|
|
nodeToProject as LTextNode | LElementNode | LContainerNode, currentParent, currentView);
|
|
|
|
nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent;
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a current view, finds the nearest component's host (LElement).
|
|
|
|
*
|
2018-01-08 20:17:13 -08:00
|
|
|
* @param lView LView for which we want a host element node
|
2017-12-14 16:26:28 -08:00
|
|
|
* @returns The host node
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-01-08 20:17:13 -08:00
|
|
|
function findComponentHost(lView: LView): LElementNode {
|
|
|
|
let viewRootLNode = lView.node;
|
2017-12-01 14:23:03 -08:00
|
|
|
while ((viewRootLNode.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.View) {
|
2018-01-08 20:17:13 -08:00
|
|
|
ngDevMode && assertNotNull(lView.parent, 'lView.parent');
|
|
|
|
lView = lView.parent !;
|
|
|
|
viewRootLNode = lView.node;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
ngDevMode && assertNodeType(viewRootLNode, LNodeFlags.Element);
|
|
|
|
ngDevMode && assertNotNull(viewRootLNode.data, 'node.data');
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
return viewRootLNode as LElementNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-01-08 20:17:13 -08:00
|
|
|
* Adds a LView or a LContainer to the end of the current view tree.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2017-12-14 16:26:28 -08:00
|
|
|
* This structure will be used to traverse through nested views to remove listeners
|
|
|
|
* and call onDestroy callbacks.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-01-08 20:17:13 -08:00
|
|
|
* @param state The LView or LContainer to add to the view tree
|
2017-12-14 16:26:28 -08:00
|
|
|
* @returns The state passed in
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-01-08 20:17:13 -08:00
|
|
|
export function addToViewTree<T extends LView|LContainer>(state: T): T {
|
2017-12-01 14:23:03 -08:00
|
|
|
currentView.tail ? (currentView.tail.next = state) : (currentView.child = state);
|
|
|
|
currentView.tail = state;
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2017-12-14 15:50:01 -08:00
|
|
|
//////////////////////////
|
|
|
|
//// Bindings
|
|
|
|
//////////////////////////
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2017-12-20 10:47:22 -08:00
|
|
|
export interface NO_CHANGE {
|
2017-12-14 15:50:01 -08:00
|
|
|
// This is a brand that ensures that this type can never match anything else
|
2017-12-20 10:47:22 -08:00
|
|
|
brand: 'NO_CHANGE';
|
2017-12-14 15:50:01 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/** A special value which designates that a value has not changed. */
|
|
|
|
export const NO_CHANGE = {} as NO_CHANGE;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create interpolation bindings with variable number of arguments.
|
|
|
|
*
|
2017-12-14 15:50:01 -08:00
|
|
|
* If any of the arguments change, then the interpolation is concatenated
|
2017-12-01 14:23:03 -08:00
|
|
|
* and causes an update.
|
|
|
|
*
|
|
|
|
* @param values an array of values to diff.
|
|
|
|
*/
|
|
|
|
export function bindV(values: any[]): string|NO_CHANGE {
|
|
|
|
let different: boolean;
|
|
|
|
let parts: any[];
|
|
|
|
if (different = creationMode) {
|
|
|
|
// make a copy of the array.
|
|
|
|
if (typeof currentView.bindingStartIndex !== 'number') {
|
2017-12-08 11:48:54 -08:00
|
|
|
bindingIndex = currentView.bindingStartIndex = data.length;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2017-12-08 11:48:54 -08:00
|
|
|
data[bindingIndex++] = parts = values.slice();
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2017-12-08 11:48:54 -08:00
|
|
|
parts = data[bindingIndex++];
|
2017-12-01 14:23:03 -08:00
|
|
|
different = false;
|
|
|
|
for (let i = 0; i < values.length; i++) {
|
|
|
|
different = different || values[i] !== NO_CHANGE && isDifferent(values[i], parts[i]);
|
|
|
|
if (different && values[i] !== NO_CHANGE) {
|
|
|
|
parts[i] = values[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (different) {
|
|
|
|
let str = stringify(parts[0]);
|
|
|
|
for (let i = 1; i < parts.length; i++) {
|
|
|
|
str += stringify(parts[i]);
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
} else {
|
|
|
|
return NO_CHANGE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-14 15:50:01 -08:00
|
|
|
// For bindings that have 0 - 7 dynamic values to watch, we can use a bind function that
|
|
|
|
// matches the number of interpolations. This is faster than using the bindV function above
|
|
|
|
// because we know ahead of time how many interpolations we'll have and don't need to
|
|
|
|
// accept the values as an array that will need to be copied and looped over.
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
|
|
|
* Create a single value binding without interpolation.
|
|
|
|
*
|
|
|
|
* @param value Value to diff
|
|
|
|
*/
|
|
|
|
export function bind<T>(value: T | NO_CHANGE): T|NO_CHANGE {
|
|
|
|
let different: boolean;
|
|
|
|
if (different = creationMode) {
|
|
|
|
if (typeof currentView.bindingStartIndex !== 'number') {
|
2017-12-08 11:48:54 -08:00
|
|
|
bindingIndex = currentView.bindingStartIndex = data.length;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2017-12-08 11:48:54 -08:00
|
|
|
data[bindingIndex++] = value;
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2017-12-08 11:48:54 -08:00
|
|
|
if (different = value !== NO_CHANGE && isDifferent(data[bindingIndex], value)) {
|
|
|
|
data[bindingIndex] = value;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
bindingIndex++;
|
|
|
|
}
|
|
|
|
return different ? value : NO_CHANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an interpolation bindings with 1 arguments.
|
|
|
|
*
|
|
|
|
* @param prefix static value used for concatenation only.
|
|
|
|
* @param value value checked for change.
|
|
|
|
* @param suffix static value used for concatenation only.
|
|
|
|
*/
|
|
|
|
export function bind1(prefix: string, value: any, suffix: string): string|NO_CHANGE {
|
|
|
|
return bind(value) === NO_CHANGE ? NO_CHANGE : prefix + stringify(value) + suffix;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an interpolation bindings with 2 arguments.
|
|
|
|
*
|
|
|
|
* @param prefix
|
|
|
|
* @param v0 value checked for change
|
|
|
|
* @param i0
|
|
|
|
* @param v1 value checked for change
|
|
|
|
* @param suffix
|
|
|
|
*/
|
|
|
|
export function bind2(prefix: string, v0: any, i0: string, v1: any, suffix: string): string|
|
|
|
|
NO_CHANGE {
|
|
|
|
let different: boolean;
|
|
|
|
if (different = creationMode) {
|
|
|
|
if (typeof currentView.bindingStartIndex !== 'number') {
|
2017-12-08 11:48:54 -08:00
|
|
|
bindingIndex = currentView.bindingStartIndex = data.length;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2017-12-18 12:39:46 -08:00
|
|
|
data[bindingIndex++] = v0;
|
|
|
|
data[bindingIndex++] = v1;
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2017-12-18 12:39:46 -08:00
|
|
|
const part0 = data[bindingIndex++];
|
|
|
|
const part1 = data[bindingIndex++];
|
|
|
|
if (v0 === NO_CHANGE) v0 = part0;
|
|
|
|
if (v1 === NO_CHANGE) v1 = part1;
|
|
|
|
if (different = (isDifferent(part0, v0) || isDifferent(part1, v1))) {
|
|
|
|
data[bindingIndex - 2] = v0;
|
|
|
|
data[bindingIndex - 1] = v1;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return different ? prefix + stringify(v0) + i0 + stringify(v1) + suffix : NO_CHANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an interpolation bindings with 3 arguments.
|
|
|
|
*
|
|
|
|
* @param prefix
|
|
|
|
* @param v0
|
|
|
|
* @param i0
|
|
|
|
* @param v1
|
|
|
|
* @param i1
|
|
|
|
* @param v2
|
|
|
|
* @param suffix
|
|
|
|
*/
|
|
|
|
export function bind3(
|
|
|
|
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): string|
|
|
|
|
NO_CHANGE {
|
|
|
|
let different: boolean;
|
|
|
|
if (different = creationMode) {
|
|
|
|
if (typeof currentView.bindingStartIndex !== 'number') {
|
2017-12-08 11:48:54 -08:00
|
|
|
bindingIndex = currentView.bindingStartIndex = data.length;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2017-12-18 12:39:46 -08:00
|
|
|
data[bindingIndex++] = v0;
|
|
|
|
data[bindingIndex++] = v1;
|
|
|
|
data[bindingIndex++] = v2;
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2017-12-18 12:39:46 -08:00
|
|
|
const part0 = data[bindingIndex++];
|
|
|
|
const part1 = data[bindingIndex++];
|
|
|
|
const part2 = data[bindingIndex++];
|
|
|
|
if (v0 === NO_CHANGE) v0 = part0;
|
|
|
|
if (v1 === NO_CHANGE) v1 = part1;
|
|
|
|
if (v2 === NO_CHANGE) v2 = part2;
|
|
|
|
if (different = (isDifferent(part0, v0) || isDifferent(part1, v1) || isDifferent(part2, v2))) {
|
|
|
|
data[bindingIndex - 3] = v0;
|
|
|
|
data[bindingIndex - 2] = v1;
|
|
|
|
data[bindingIndex - 1] = v2;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return different ? prefix + stringify(v0) + i0 + stringify(v1) + i1 + stringify(v2) + suffix :
|
|
|
|
NO_CHANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an interpolation binding with 4 arguments.
|
|
|
|
*
|
|
|
|
* @param prefix
|
|
|
|
* @param v0
|
|
|
|
* @param i0
|
|
|
|
* @param v1
|
|
|
|
* @param i1
|
|
|
|
* @param v2
|
|
|
|
* @param i2
|
|
|
|
* @param v3
|
|
|
|
* @param suffix
|
|
|
|
*/
|
|
|
|
export function bind4(
|
|
|
|
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any,
|
|
|
|
suffix: string): string|NO_CHANGE {
|
|
|
|
let different: boolean;
|
|
|
|
if (different = creationMode) {
|
|
|
|
if (typeof currentView.bindingStartIndex !== 'number') {
|
2017-12-08 11:48:54 -08:00
|
|
|
bindingIndex = currentView.bindingStartIndex = data.length;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2017-12-18 12:39:46 -08:00
|
|
|
data[bindingIndex++] = v0;
|
|
|
|
data[bindingIndex++] = v1;
|
|
|
|
data[bindingIndex++] = v2;
|
|
|
|
data[bindingIndex++] = v3;
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2017-12-18 12:39:46 -08:00
|
|
|
const part0 = data[bindingIndex++];
|
|
|
|
const part1 = data[bindingIndex++];
|
|
|
|
const part2 = data[bindingIndex++];
|
|
|
|
const part3 = data[bindingIndex++];
|
|
|
|
if (v0 === NO_CHANGE) v0 = part0;
|
|
|
|
if (v1 === NO_CHANGE) v1 = part1;
|
|
|
|
if (v2 === NO_CHANGE) v2 = part2;
|
|
|
|
if (v3 === NO_CHANGE) v3 = part3;
|
2017-12-01 14:23:03 -08:00
|
|
|
if (different =
|
2017-12-18 12:39:46 -08:00
|
|
|
(isDifferent(part0, v0) || isDifferent(part1, v1) || isDifferent(part2, v2) ||
|
|
|
|
isDifferent(part3, v3))) {
|
|
|
|
data[bindingIndex - 4] = v0;
|
|
|
|
data[bindingIndex - 3] = v1;
|
|
|
|
data[bindingIndex - 2] = v2;
|
|
|
|
data[bindingIndex - 1] = v3;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return different ?
|
|
|
|
prefix + stringify(v0) + i0 + stringify(v1) + i1 + stringify(v2) + i2 + stringify(v3) +
|
|
|
|
suffix :
|
|
|
|
NO_CHANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an interpolation binding with 5 arguments.
|
|
|
|
*
|
|
|
|
* @param prefix
|
|
|
|
* @param v0
|
|
|
|
* @param i0
|
|
|
|
* @param v1
|
|
|
|
* @param i1
|
|
|
|
* @param v2
|
|
|
|
* @param i2
|
|
|
|
* @param v3
|
|
|
|
* @param i3
|
|
|
|
* @param v4
|
|
|
|
* @param suffix
|
|
|
|
*/
|
|
|
|
export function bind5(
|
|
|
|
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any,
|
|
|
|
i3: string, v4: any, suffix: string): string|NO_CHANGE {
|
|
|
|
let different: boolean;
|
|
|
|
if (different = creationMode) {
|
|
|
|
if (typeof currentView.bindingStartIndex !== 'number') {
|
2017-12-08 11:48:54 -08:00
|
|
|
bindingIndex = currentView.bindingStartIndex = data.length;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2017-12-18 12:39:46 -08:00
|
|
|
data[bindingIndex++] = v0;
|
|
|
|
data[bindingIndex++] = v1;
|
|
|
|
data[bindingIndex++] = v2;
|
|
|
|
data[bindingIndex++] = v3;
|
|
|
|
data[bindingIndex++] = v4;
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2017-12-18 12:39:46 -08:00
|
|
|
const part0 = data[bindingIndex++];
|
|
|
|
const part1 = data[bindingIndex++];
|
|
|
|
const part2 = data[bindingIndex++];
|
|
|
|
const part3 = data[bindingIndex++];
|
|
|
|
const part4 = data[bindingIndex++];
|
|
|
|
if (v0 === NO_CHANGE) v0 = part0;
|
|
|
|
if (v1 === NO_CHANGE) v1 = part1;
|
|
|
|
if (v2 === NO_CHANGE) v2 = part2;
|
|
|
|
if (v3 === NO_CHANGE) v3 = part3;
|
|
|
|
if (v4 === NO_CHANGE) v4 = part4;
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
if (different =
|
2017-12-18 12:39:46 -08:00
|
|
|
(isDifferent(part0, v0) || isDifferent(part1, v1) || isDifferent(part2, v2) ||
|
|
|
|
isDifferent(part3, v3) || isDifferent(part4, v4))) {
|
|
|
|
data[bindingIndex - 5] = v0;
|
|
|
|
data[bindingIndex - 4] = v1;
|
|
|
|
data[bindingIndex - 3] = v2;
|
|
|
|
data[bindingIndex - 2] = v3;
|
|
|
|
data[bindingIndex - 1] = v4;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return different ?
|
|
|
|
prefix + stringify(v0) + i0 + stringify(v1) + i1 + stringify(v2) + i2 + stringify(v3) + i3 +
|
|
|
|
stringify(v4) + suffix :
|
|
|
|
NO_CHANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an interpolation binding with 6 arguments.
|
|
|
|
*
|
|
|
|
* @param prefix
|
|
|
|
* @param v0
|
|
|
|
* @param i0
|
|
|
|
* @param v1
|
|
|
|
* @param i1
|
|
|
|
* @param v2
|
|
|
|
* @param i2
|
|
|
|
* @param v3
|
|
|
|
* @param i3
|
|
|
|
* @param v4
|
|
|
|
* @param i4
|
|
|
|
* @param v5
|
|
|
|
* @param suffix
|
|
|
|
*/
|
|
|
|
export function bind6(
|
|
|
|
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any,
|
|
|
|
i3: string, v4: any, i4: string, v5: any, suffix: string): string|NO_CHANGE {
|
|
|
|
let different: boolean;
|
|
|
|
if (different = creationMode) {
|
|
|
|
if (typeof currentView.bindingStartIndex !== 'number') {
|
2017-12-08 11:48:54 -08:00
|
|
|
bindingIndex = currentView.bindingStartIndex = data.length;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2017-12-18 12:39:46 -08:00
|
|
|
data[bindingIndex++] = v0;
|
|
|
|
data[bindingIndex++] = v1;
|
|
|
|
data[bindingIndex++] = v2;
|
|
|
|
data[bindingIndex++] = v3;
|
|
|
|
data[bindingIndex++] = v4;
|
|
|
|
data[bindingIndex++] = v5;
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2017-12-18 12:39:46 -08:00
|
|
|
const part0 = data[bindingIndex++];
|
|
|
|
const part1 = data[bindingIndex++];
|
|
|
|
const part2 = data[bindingIndex++];
|
|
|
|
const part3 = data[bindingIndex++];
|
|
|
|
const part4 = data[bindingIndex++];
|
|
|
|
const part5 = data[bindingIndex++];
|
|
|
|
if (v0 === NO_CHANGE) v0 = part0;
|
|
|
|
if (v1 === NO_CHANGE) v1 = part1;
|
|
|
|
if (v2 === NO_CHANGE) v2 = part2;
|
|
|
|
if (v3 === NO_CHANGE) v3 = part3;
|
|
|
|
if (v4 === NO_CHANGE) v4 = part4;
|
|
|
|
if (v5 === NO_CHANGE) v5 = part5;
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
if (different =
|
2017-12-18 12:39:46 -08:00
|
|
|
(isDifferent(part0, v0) || isDifferent(part1, v1) || isDifferent(part2, v2) ||
|
|
|
|
isDifferent(part3, v3) || isDifferent(part4, v4) || isDifferent(part5, v5))) {
|
|
|
|
data[bindingIndex - 6] = v0;
|
|
|
|
data[bindingIndex - 5] = v1;
|
|
|
|
data[bindingIndex - 4] = v2;
|
|
|
|
data[bindingIndex - 3] = v3;
|
|
|
|
data[bindingIndex - 2] = v4;
|
|
|
|
data[bindingIndex - 1] = v5;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return different ?
|
|
|
|
prefix + stringify(v0) + i0 + stringify(v1) + i1 + stringify(v2) + i2 + stringify(v3) + i3 +
|
|
|
|
stringify(v4) + i4 + stringify(v5) + suffix :
|
|
|
|
NO_CHANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an interpolation binding with 7 arguments.
|
|
|
|
*
|
|
|
|
* @param prefix
|
|
|
|
* @param v0
|
|
|
|
* @param i0
|
|
|
|
* @param v1
|
|
|
|
* @param i1
|
|
|
|
* @param v2
|
|
|
|
* @param i2
|
|
|
|
* @param v3
|
|
|
|
* @param i3
|
|
|
|
* @param v4
|
|
|
|
* @param i4
|
|
|
|
* @param v5
|
|
|
|
* @param i5
|
|
|
|
* @param v6
|
|
|
|
* @param suffix
|
|
|
|
*/
|
|
|
|
export function bind7(
|
|
|
|
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any,
|
|
|
|
i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): string|
|
|
|
|
NO_CHANGE {
|
|
|
|
let different: boolean;
|
|
|
|
if (different = creationMode) {
|
|
|
|
if (typeof currentView.bindingStartIndex !== 'number') {
|
2017-12-08 11:48:54 -08:00
|
|
|
bindingIndex = currentView.bindingStartIndex = data.length;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2017-12-18 12:39:46 -08:00
|
|
|
data[bindingIndex++] = v0;
|
|
|
|
data[bindingIndex++] = v1;
|
|
|
|
data[bindingIndex++] = v2;
|
|
|
|
data[bindingIndex++] = v3;
|
|
|
|
data[bindingIndex++] = v4;
|
|
|
|
data[bindingIndex++] = v5;
|
|
|
|
data[bindingIndex++] = v6;
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2017-12-18 12:39:46 -08:00
|
|
|
const part0 = data[bindingIndex++];
|
|
|
|
const part1 = data[bindingIndex++];
|
|
|
|
const part2 = data[bindingIndex++];
|
|
|
|
const part3 = data[bindingIndex++];
|
|
|
|
const part4 = data[bindingIndex++];
|
|
|
|
const part5 = data[bindingIndex++];
|
|
|
|
const part6 = data[bindingIndex++];
|
|
|
|
if (v0 === NO_CHANGE) v0 = part0;
|
|
|
|
if (v1 === NO_CHANGE) v1 = part1;
|
|
|
|
if (v2 === NO_CHANGE) v2 = part2;
|
|
|
|
if (v3 === NO_CHANGE) v3 = part3;
|
|
|
|
if (v4 === NO_CHANGE) v4 = part4;
|
|
|
|
if (v5 === NO_CHANGE) v5 = part5;
|
|
|
|
if (v6 === NO_CHANGE) v6 = part6;
|
2017-12-01 14:23:03 -08:00
|
|
|
if (different =
|
2017-12-18 12:39:46 -08:00
|
|
|
(isDifferent(part0, v0) || isDifferent(part1, v1) || isDifferent(part2, v2) ||
|
|
|
|
isDifferent(part3, v3) || isDifferent(part4, v4) || isDifferent(part5, v5) ||
|
|
|
|
isDifferent(part6, v6))) {
|
|
|
|
data[bindingIndex - 7] = v0;
|
|
|
|
data[bindingIndex - 6] = v1;
|
|
|
|
data[bindingIndex - 5] = v2;
|
|
|
|
data[bindingIndex - 4] = v3;
|
|
|
|
data[bindingIndex - 3] = v4;
|
|
|
|
data[bindingIndex - 2] = v5;
|
|
|
|
data[bindingIndex - 1] = v6;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return different ?
|
|
|
|
prefix + stringify(v0) + i0 + stringify(v1) + i1 + stringify(v2) + i2 + stringify(v3) + i3 +
|
|
|
|
stringify(v4) + i4 + stringify(v5) + i5 + stringify(v6) + suffix :
|
|
|
|
NO_CHANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an interpolation binding with 8 arguments.
|
|
|
|
*
|
|
|
|
* @param prefix
|
|
|
|
* @param v0
|
|
|
|
* @param i0
|
|
|
|
* @param v1
|
|
|
|
* @param i1
|
|
|
|
* @param v2
|
|
|
|
* @param i2
|
|
|
|
* @param v3
|
|
|
|
* @param i3
|
|
|
|
* @param v4
|
|
|
|
* @param i4
|
|
|
|
* @param v5
|
|
|
|
* @param i5
|
|
|
|
* @param v6
|
|
|
|
* @param i6
|
|
|
|
* @param v7
|
|
|
|
* @param suffix
|
|
|
|
*/
|
|
|
|
export function bind8(
|
|
|
|
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any,
|
|
|
|
i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any,
|
|
|
|
suffix: string): string|NO_CHANGE {
|
|
|
|
let different: boolean;
|
|
|
|
if (different = creationMode) {
|
|
|
|
if (typeof currentView.bindingStartIndex !== 'number') {
|
2017-12-08 11:48:54 -08:00
|
|
|
bindingIndex = currentView.bindingStartIndex = data.length;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2017-12-18 12:39:46 -08:00
|
|
|
data[bindingIndex++] = v0;
|
|
|
|
data[bindingIndex++] = v1;
|
|
|
|
data[bindingIndex++] = v2;
|
|
|
|
data[bindingIndex++] = v3;
|
|
|
|
data[bindingIndex++] = v4;
|
|
|
|
data[bindingIndex++] = v5;
|
|
|
|
data[bindingIndex++] = v6;
|
|
|
|
data[bindingIndex++] = v7;
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2017-12-18 12:39:46 -08:00
|
|
|
const part0 = data[bindingIndex++];
|
|
|
|
const part1 = data[bindingIndex++];
|
|
|
|
const part2 = data[bindingIndex++];
|
|
|
|
const part3 = data[bindingIndex++];
|
|
|
|
const part4 = data[bindingIndex++];
|
|
|
|
const part5 = data[bindingIndex++];
|
|
|
|
const part6 = data[bindingIndex++];
|
|
|
|
const part7 = data[bindingIndex++];
|
|
|
|
if (v0 === NO_CHANGE) v0 = part0;
|
|
|
|
if (v1 === NO_CHANGE) v1 = part1;
|
|
|
|
if (v2 === NO_CHANGE) v2 = part2;
|
|
|
|
if (v3 === NO_CHANGE) v3 = part3;
|
|
|
|
if (v4 === NO_CHANGE) v4 = part4;
|
|
|
|
if (v5 === NO_CHANGE) v5 = part5;
|
|
|
|
if (v6 === NO_CHANGE) v6 = part6;
|
|
|
|
if (v7 === NO_CHANGE) v7 = part7;
|
2017-12-01 14:23:03 -08:00
|
|
|
if (different =
|
2017-12-18 12:39:46 -08:00
|
|
|
(isDifferent(part0, v0) || isDifferent(part1, v1) || isDifferent(part2, v2) ||
|
|
|
|
isDifferent(part3, v3) || isDifferent(part4, v4) || isDifferent(part5, v5) ||
|
|
|
|
isDifferent(part6, v6) || isDifferent(part7, v7))) {
|
|
|
|
data[bindingIndex - 8] = v0;
|
|
|
|
data[bindingIndex - 7] = v1;
|
|
|
|
data[bindingIndex - 6] = v2;
|
|
|
|
data[bindingIndex - 5] = v3;
|
|
|
|
data[bindingIndex - 4] = v4;
|
|
|
|
data[bindingIndex - 3] = v5;
|
|
|
|
data[bindingIndex - 2] = v6;
|
|
|
|
data[bindingIndex - 1] = v7;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return different ?
|
|
|
|
prefix + stringify(v0) + i0 + stringify(v1) + i1 + stringify(v2) + i2 + stringify(v3) + i3 +
|
|
|
|
stringify(v4) + i4 + stringify(v5) + i5 + stringify(v6) + i6 + stringify(v7) + suffix :
|
|
|
|
NO_CHANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function memory<T>(index: number, value?: T): T {
|
2017-12-08 11:48:54 -08:00
|
|
|
return valueInData<T>(data, index, value);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2017-12-08 11:48:54 -08:00
|
|
|
function valueInData<T>(data: any[], index: number, value?: T): T {
|
2017-12-01 14:23:03 -08:00
|
|
|
if (value === undefined) {
|
2018-01-17 09:21:25 -08:00
|
|
|
ngDevMode && assertDataInRange(index, data);
|
2017-12-08 11:48:54 -08:00
|
|
|
value = data[index];
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2017-12-11 14:08:52 -08:00
|
|
|
// We don't store any static data for local variables, so the first time
|
|
|
|
// we see the template, we should store as null to avoid a sparse array
|
2018-01-10 18:19:16 -08:00
|
|
|
if (index >= tData.length) {
|
|
|
|
tData[index] = null;
|
2017-12-11 14:08:52 -08:00
|
|
|
}
|
2017-12-08 11:48:54 -08:00
|
|
|
data[index] = value;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
return value !;
|
|
|
|
}
|
|
|
|
|
2018-01-17 09:45:40 -08:00
|
|
|
export function getCurrentQuery(QueryType: {new (): LQuery}): LQuery {
|
|
|
|
return currentQuery || (currentQuery = new QueryType());
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-01-17 09:45:40 -08:00
|
|
|
export function getPreviousOrParentNode(): LNode {
|
|
|
|
return previousOrParentNode;
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-17 10:09:05 -08:00
|
|
|
export function getRenderer(): Renderer3 {
|
|
|
|
return renderer;
|
|
|
|
}
|
|
|
|
|
2018-01-17 09:45:40 -08:00
|
|
|
export function assertPreviousIsParent() {
|
2017-12-01 14:23:03 -08:00
|
|
|
assertEqual(isParent, true, 'isParent');
|
|
|
|
}
|
|
|
|
|
|
|
|
function assertHasParent() {
|
|
|
|
assertNotEqual(previousOrParentNode.parent, null, 'isParent');
|
|
|
|
}
|
|
|
|
|
2017-12-08 11:48:54 -08:00
|
|
|
function assertDataInRange(index: number, arr?: any[]) {
|
|
|
|
if (arr == null) arr = data;
|
2018-01-17 09:21:25 -08:00
|
|
|
assertLessThan(index, arr ? arr.length : 0, 'data.length');
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-01-18 13:27:01 -08:00
|
|
|
|
|
|
|
function assertDataNext(index: number) {
|
|
|
|
assertEqual(data.length, index, 'data.length not in sequence');
|
2018-01-17 17:55:55 +01:00
|
|
|
}
|