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';
|
|
|
|
|
2018-05-09 16:49:39 -07:00
|
|
|
import {Sanitizer} from '../sanitization/security';
|
|
|
|
|
|
|
|
import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotEqual, assertSame} from './assert';
|
|
|
|
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
|
|
|
|
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
|
2018-04-26 10:44:49 -07:00
|
|
|
import {LContainer} from './interfaces/container';
|
2018-02-26 16:58:15 -08:00
|
|
|
import {LInjector} from './interfaces/injector';
|
2018-03-29 16:41:45 -07:00
|
|
|
import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
|
2018-01-29 14:51:37 +01:00
|
|
|
import {LQueries} from './interfaces/query';
|
2018-06-01 18:54:23 -07:00
|
|
|
import {CurrentMatchesList, LView, LViewFlags, RootContext, TData, TView} from './interfaces/view';
|
2017-12-14 15:03:46 -08:00
|
|
|
|
2018-05-04 15:58:42 +02:00
|
|
|
import {AttributeMarker, TAttributes, LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue, TElementNode,} from './interfaces/node';
|
2018-01-31 15:50:24 +01:00
|
|
|
import {assertNodeType} from './node_assert';
|
2018-05-30 13:43:14 -07:00
|
|
|
import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode, getLViewChild} from './node_manipulation';
|
2018-03-29 16:41:45 -07:00
|
|
|
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
2018-06-06 13:38:21 -07:00
|
|
|
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, PipeDefList, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
|
|
|
import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
2017-12-01 14:23:03 -08:00
|
|
|
import {isDifferent, stringify} from './util';
|
2018-02-26 16:58:15 -08:00
|
|
|
import {ViewRef} from './view_ref';
|
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__';
|
|
|
|
|
2018-02-23 13:17:20 -08:00
|
|
|
/**
|
|
|
|
* A permanent marker promise which signifies that the current CD tree is
|
|
|
|
* clean.
|
|
|
|
*/
|
|
|
|
const _CLEAN_PROMISE = Promise.resolve(null);
|
|
|
|
|
2018-03-01 17:14:01 -08:00
|
|
|
/**
|
|
|
|
* Function used to sanitize the value before writing it into the renderer.
|
|
|
|
*/
|
2018-05-09 15:30:16 -07:00
|
|
|
export type SanitizerFn = (value: any) => string;
|
2018-03-01 17:14:01 -08:00
|
|
|
|
2018-03-16 16:42:13 -07:00
|
|
|
/**
|
|
|
|
* Directive and element indices for top-level directive.
|
|
|
|
*
|
|
|
|
* Saved here to avoid re-instantiating an array on every change detection run.
|
|
|
|
*/
|
2018-03-25 21:32:39 -07:00
|
|
|
export const _ROOT_DIRECTIVE_INDICES = [0, 0];
|
2018-03-16 16:42:13 -07:00
|
|
|
|
2018-04-04 21:21:12 -07:00
|
|
|
/**
|
|
|
|
* Token set in currentMatches while dependencies are being resolved.
|
|
|
|
*
|
|
|
|
* If we visit a directive that has a value set to CIRCULAR, we know we've
|
|
|
|
* already seen it, and thus have a circular dependency.
|
|
|
|
*/
|
|
|
|
export const CIRCULAR = '__CIRCULAR__';
|
2018-02-23 13:17:20 -08:00
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
2018-02-16 21:06:23 -08:00
|
|
|
export function getRenderer(): Renderer3 {
|
2018-05-31 15:45:46 +02:00
|
|
|
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
2018-02-16 21:06:23 -08:00
|
|
|
return renderer;
|
|
|
|
}
|
|
|
|
|
2018-05-09 15:30:16 -07:00
|
|
|
export function getCurrentSanitizer(): Sanitizer|null {
|
|
|
|
return currentView && currentView.sanitizer;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/** Used to set the parent property when nodes are created. */
|
|
|
|
let previousOrParentNode: LNode;
|
|
|
|
|
2018-02-16 21:06:23 -08:00
|
|
|
export function getPreviousOrParentNode(): LNode {
|
2018-05-31 15:45:46 +02:00
|
|
|
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
2018-02-16 21:06:23 -08:00
|
|
|
return previousOrParentNode;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
|
|
|
* 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
|
2018-03-21 15:10:34 -07:00
|
|
|
* 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-02-03 20:34:30 -08:00
|
|
|
/**
|
|
|
|
* State of the current view being processed.
|
|
|
|
*
|
|
|
|
* NOTE: we cheat here and initialize it to `null` even thought the type does not
|
|
|
|
* contain `null`. This is because we expect this value to be not `null` as soon
|
|
|
|
* as we enter the view. Declaring the type as `null` would require us to place `!`
|
|
|
|
* in most instructions since they all assume that `currentView` is defined.
|
|
|
|
*/
|
|
|
|
let currentView: LView = null !;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-29 14:51:37 +01:00
|
|
|
let currentQueries: LQueries|null;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-02-16 21:06:23 -08:00
|
|
|
export function getCurrentQueries(QueryType: {new (): LQueries}): LQueries {
|
2018-05-31 15:45:46 +02:00
|
|
|
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
2018-02-16 21:06:23 -08:00
|
|
|
return currentQueries || (currentQueries = new QueryType());
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
|
|
|
* This property gets set before entering a template.
|
|
|
|
*/
|
|
|
|
let creationMode: boolean;
|
|
|
|
|
2018-02-16 21:06:23 -08:00
|
|
|
export function getCreationMode(): boolean {
|
2018-05-31 15:45:46 +02:00
|
|
|
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
2018-02-16 21:06:23 -08:00
|
|
|
return creationMode;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
2018-02-16 16:23:27 +01:00
|
|
|
* An array of nodes (text, element, container, etc), pipes, their bindings, and
|
2017-12-08 11:48:54 -08:00
|
|
|
* 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
|
|
|
|
2018-03-21 15:10:34 -07:00
|
|
|
/**
|
|
|
|
* An array of directive instances in the current view.
|
|
|
|
*
|
|
|
|
* These must be stored separately from LNodes because their presence is
|
|
|
|
* unknown at compile-time and thus space cannot be reserved in data[].
|
|
|
|
*/
|
|
|
|
let directives: any[]|null;
|
|
|
|
|
2018-06-05 15:28:15 -07:00
|
|
|
function getCleanup(view: LView): any[] {
|
2018-05-31 15:45:46 +02:00
|
|
|
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
2018-06-05 15:28:15 -07:00
|
|
|
return view.cleanupInstances || (view.cleanupInstances = []);
|
2018-05-31 15:45:46 +02:00
|
|
|
}
|
|
|
|
|
2018-06-05 15:28:15 -07:00
|
|
|
function getTViewCleanup(view: LView): any[] {
|
|
|
|
return view.tView.cleanup || (view.tView.cleanup = []);
|
|
|
|
}
|
2018-03-09 20:22:18 -08:00
|
|
|
/**
|
|
|
|
* In this mode, any changes in bindings will throw an ExpressionChangedAfterChecked error.
|
|
|
|
*
|
|
|
|
* Necessary to support ChangeDetectorRef.checkNoChanges().
|
|
|
|
*/
|
|
|
|
let checkNoChangesMode = false;
|
|
|
|
|
2018-03-16 16:42:13 -07:00
|
|
|
/** Whether or not this is the first time the current view has been processed. */
|
|
|
|
let firstTemplatePass = true;
|
|
|
|
|
2018-02-07 22:19:24 -08:00
|
|
|
const enum BindingDirection {
|
|
|
|
Input,
|
|
|
|
Output,
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
|
|
|
* 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 {
|
2018-04-03 15:18:05 -07:00
|
|
|
const oldView: LView = currentView;
|
2018-02-03 20:34:30 -08:00
|
|
|
data = newView && newView.data;
|
2018-03-21 15:10:34 -07:00
|
|
|
directives = newView && newView.directives;
|
2018-02-03 20:34:30 -08:00
|
|
|
tData = newView && newView.tView.data;
|
2018-02-23 13:17:20 -08:00
|
|
|
creationMode = newView && (newView.flags & LViewFlags.CreationMode) === LViewFlags.CreationMode;
|
2018-03-16 16:42:13 -07:00
|
|
|
firstTemplatePass = newView && newView.tView.firstTemplatePass;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-02-03 20:34:30 -08:00
|
|
|
renderer = newView && 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-02-03 20:34:30 -08:00
|
|
|
currentQueries = newView && newView.queries;
|
2018-01-17 17:55:55 +01:00
|
|
|
|
2018-04-03 15:18:05 -07: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-04-12 13:49:37 +02:00
|
|
|
*
|
|
|
|
* @param newView New state to become active
|
|
|
|
* @param creationOnly An optional boolean to indicate that the view was processed in creation mode
|
|
|
|
* only, i.e. the first update will be done later. Only possible for dynamically created views.
|
|
|
|
*/
|
|
|
|
export function leaveView(newView: LView, creationOnly?: boolean): void {
|
|
|
|
if (!creationOnly) {
|
|
|
|
if (!checkNoChangesMode) {
|
|
|
|
executeHooks(
|
|
|
|
directives !, currentView.tView.viewHooks, currentView.tView.viewCheckHooks,
|
|
|
|
creationMode);
|
|
|
|
}
|
|
|
|
// Views are clean and in update mode after being checked, so these bits are cleared
|
|
|
|
currentView.flags &= ~(LViewFlags.CreationMode | LViewFlags.Dirty);
|
2018-03-09 20:22:18 -08:00
|
|
|
}
|
2018-06-01 18:54:23 -07:00
|
|
|
currentView.flags |= LViewFlags.RunInit;
|
2018-04-10 20:57:20 -07:00
|
|
|
currentView.bindingIndex = -1;
|
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-04-12 13:49:37 +02:00
|
|
|
/**
|
|
|
|
* Refreshes the view, executing the following steps in that order:
|
|
|
|
* triggers init hooks, refreshes dynamic children, triggers content hooks, sets host bindings,
|
|
|
|
* refreshes child components.
|
|
|
|
* Note: view hooks are triggered later when leaving the view.
|
|
|
|
* */
|
|
|
|
function refreshView() {
|
2018-03-16 16:42:13 -07:00
|
|
|
const tView = currentView.tView;
|
2018-04-12 13:49:37 +02:00
|
|
|
if (!checkNoChangesMode) {
|
|
|
|
executeInitHooks(currentView, tView, creationMode);
|
|
|
|
}
|
|
|
|
refreshDynamicChildren();
|
|
|
|
if (!checkNoChangesMode) {
|
|
|
|
executeHooks(directives !, tView.contentHooks, tView.contentCheckHooks, creationMode);
|
|
|
|
}
|
|
|
|
|
2018-03-13 11:48:09 -07:00
|
|
|
// This needs to be set before children are processed to support recursive components
|
2018-03-16 16:42:13 -07:00
|
|
|
tView.firstTemplatePass = firstTemplatePass = false;
|
|
|
|
|
|
|
|
setHostBindings(tView.hostBindings);
|
|
|
|
refreshChildComponents(tView.components);
|
|
|
|
}
|
2018-03-13 11:48:09 -07:00
|
|
|
|
2018-03-16 16:42:13 -07:00
|
|
|
/** Sets the host bindings for the current view. */
|
2018-03-25 21:32:39 -07:00
|
|
|
export function setHostBindings(bindings: number[] | null): void {
|
2018-03-16 16:42:13 -07:00
|
|
|
if (bindings != null) {
|
2018-03-21 15:10:34 -07:00
|
|
|
const defs = currentView.tView.directives !;
|
2018-03-16 16:42:13 -07:00
|
|
|
for (let i = 0; i < bindings.length; i += 2) {
|
|
|
|
const dirIndex = bindings[i];
|
2018-04-13 23:02:29 -07:00
|
|
|
const def = defs[dirIndex] as DirectiveDef<any>;
|
2018-04-03 15:18:05 -07:00
|
|
|
def.hostBindings && def.hostBindings(dirIndex, bindings[i + 1]);
|
2018-03-16 16:42:13 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Refreshes child components in the current view. */
|
|
|
|
function refreshChildComponents(components: number[] | null): void {
|
2018-03-13 11:48:09 -07:00
|
|
|
if (components != null) {
|
2018-03-21 15:10:34 -07:00
|
|
|
for (let i = 0; i < components.length; i += 2) {
|
2018-04-03 15:18:05 -07:00
|
|
|
componentRefresh(components[i], components[i + 1]);
|
2018-03-13 11:48:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-25 21:32:39 -07:00
|
|
|
export function executeInitAndContentHooks(): void {
|
2018-03-13 11:48:09 -07:00
|
|
|
if (!checkNoChangesMode) {
|
|
|
|
const tView = currentView.tView;
|
|
|
|
executeInitHooks(currentView, tView, creationMode);
|
2018-03-21 15:10:34 -07:00
|
|
|
executeHooks(directives !, tView.contentHooks, tView.contentCheckHooks, creationMode);
|
2018-03-13 11:48:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-04 09:22:53 -07:00
|
|
|
export function createLView<T>(
|
2018-06-04 13:07:09 -07:00
|
|
|
renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags,
|
|
|
|
sanitizer?: Sanitizer | null): LView {
|
2017-12-01 14:23:03 -08:00
|
|
|
const newView = {
|
|
|
|
parent: currentView,
|
2018-06-01 18:54:23 -07:00
|
|
|
flags: flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit,
|
2017-12-08 11:48:54 -08:00
|
|
|
node: null !, // until we initialize it in createNode.
|
2017-12-22 16:41:34 -08:00
|
|
|
data: [],
|
2018-03-21 15:10:34 -07:00
|
|
|
directives: null,
|
2018-01-10 18:19:16 -08:00
|
|
|
tView: tView,
|
2018-06-05 15:28:15 -07:00
|
|
|
cleanupInstances: null,
|
2017-12-01 14:23:03 -08:00
|
|
|
renderer: renderer,
|
|
|
|
tail: null,
|
|
|
|
next: null,
|
2018-04-10 20:57:20 -07:00
|
|
|
bindingIndex: -1,
|
2018-01-17 10:09:05 -08:00
|
|
|
context: context,
|
2018-01-29 14:51:37 +01:00
|
|
|
queries: null,
|
2018-04-12 15:54:16 -07:00
|
|
|
injector: currentView && currentView.injector,
|
2018-05-09 15:30:16 -07:00
|
|
|
sanitizer: sanitizer || null
|
2017-12-01 14:23:03 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
return newView;
|
|
|
|
}
|
|
|
|
|
2018-03-15 17:33:35 +01:00
|
|
|
/**
|
|
|
|
* Creation of LNode object is extracted to a separate function so we always create LNode object
|
|
|
|
* with the same shape
|
|
|
|
* (same properties assigned in the same order).
|
|
|
|
*/
|
|
|
|
export function createLNodeObject(
|
2018-05-29 15:08:30 -07:00
|
|
|
type: TNodeType, currentView: LView, parent: LNode | null,
|
|
|
|
native: RText | RElement | null | undefined, state: any,
|
2018-03-15 17:33:35 +01:00
|
|
|
queries: LQueries | null): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode {
|
|
|
|
return {
|
|
|
|
native: native as any,
|
|
|
|
view: currentView,
|
|
|
|
nodeInjector: parent ? parent.nodeInjector : null,
|
|
|
|
data: state,
|
|
|
|
queries: queries,
|
2018-05-16 05:56:01 -07:00
|
|
|
tNode: null !,
|
2018-04-03 10:18:25 +02:00
|
|
|
pNextOrParent: null,
|
|
|
|
dynamicLContainerNode: null
|
2018-03-15 17:33:35 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
|
|
|
* 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.
|
2018-05-11 20:57:37 -07:00
|
|
|
*
|
|
|
|
* @param index The index at which the LNode should be saved (null if view, since they are not
|
|
|
|
* saved)
|
|
|
|
* @param type The type of LNode to create
|
|
|
|
* @param native The native element for this LNode, if applicable
|
|
|
|
* @param name The tag name of the associated native element, if applicable
|
|
|
|
* @param attrs Any attrs for the native element, if applicable
|
|
|
|
* @param data Any data that should be saved on the LNode
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2017-12-08 11:48:54 -08:00
|
|
|
export function createLNode(
|
2018-06-01 14:46:28 -07:00
|
|
|
index: number, type: TNodeType.Element, native: RElement | RText | null, name: string | null,
|
|
|
|
attrs: TAttributes | null, lView?: LView | null): LElementNode;
|
2017-12-08 11:48:54 -08:00
|
|
|
export function createLNode(
|
2018-06-01 14:46:28 -07:00
|
|
|
index: number, type: TNodeType.View, native: null, name: null, attrs: null,
|
2018-05-11 20:57:37 -07:00
|
|
|
lView: LView): LViewNode;
|
2017-12-08 11:48:54 -08:00
|
|
|
export function createLNode(
|
2018-05-17 12:54:57 -07:00
|
|
|
index: number, type: TNodeType.Container, native: undefined, name: string | null,
|
2018-05-04 15:58:42 +02:00
|
|
|
attrs: TAttributes | null, lContainer: LContainer): LContainerNode;
|
2017-12-08 11:48:54 -08:00
|
|
|
export function createLNode(
|
2018-05-04 15:58:42 +02:00
|
|
|
index: number, type: TNodeType.Projection, native: null, name: null, attrs: TAttributes | null,
|
2018-01-08 20:17:13 -08:00
|
|
|
lProjection: LProjection): LProjectionNode;
|
2017-12-08 11:48:54 -08:00
|
|
|
export function createLNode(
|
2018-06-01 14:46:28 -07:00
|
|
|
index: number, type: TNodeType, native: RText | RElement | null | undefined,
|
2018-05-04 15:58:42 +02:00
|
|
|
name: string | null, attrs: TAttributes | null, state?: null | LView | LContainer |
|
2018-05-11 20:57:37 -07:00
|
|
|
LProjection): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode {
|
2017-12-01 14:23:03 -08:00
|
|
|
const parent = isParent ? previousOrParentNode :
|
2018-05-29 15:08:30 -07:00
|
|
|
previousOrParentNode && getParentLNode(previousOrParentNode) !as LNode;
|
|
|
|
// Parents cannot cross component boundaries because components will be used in multiple places,
|
|
|
|
// so it's only set if the view is the same.
|
|
|
|
const tParent =
|
|
|
|
parent && parent.view === currentView ? parent.tNode as TElementNode | TContainerNode : null;
|
2018-01-29 14:51:37 +01:00
|
|
|
let queries =
|
|
|
|
(isParent ? currentQueries : previousOrParentNode && previousOrParentNode.queries) ||
|
|
|
|
parent && parent.queries && parent.queries.child();
|
2017-12-01 14:23:03 -08:00
|
|
|
const isState = state != null;
|
2018-03-15 17:33:35 +01:00
|
|
|
const node =
|
|
|
|
createLNodeObject(type, currentView, parent, native, isState ? state as any : null, queries);
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-06-01 14:46:28 -07:00
|
|
|
if (index === -1 || type === TNodeType.View) {
|
2018-05-16 05:56:01 -07:00
|
|
|
// View nodes are not stored in data because they can be added / removed at runtime (which
|
|
|
|
// would cause indices to change). Their TNodes are instead stored in TView.node.
|
2018-05-29 15:08:30 -07:00
|
|
|
node.tNode = (state as LView).tView.node || createTNode(type, index, null, null, tParent, null);
|
2018-05-16 05:56:01 -07:00
|
|
|
} else {
|
|
|
|
// This is an element or container or projection node
|
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) {
|
2018-05-29 15:08:30 -07:00
|
|
|
const tNode = tData[index] = createTNode(type, index, name, attrs, tParent, null);
|
2018-05-11 20:57:37 -07:00
|
|
|
if (!isParent && previousOrParentNode) {
|
2018-05-16 05:56:01 -07:00
|
|
|
const previousTNode = previousOrParentNode.tNode;
|
|
|
|
previousTNode.next = tNode;
|
|
|
|
if (previousTNode.dynamicContainerNode) previousTNode.dynamicContainerNode.next = tNode;
|
2018-05-11 20:57:37 -07:00
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-05-11 20:57:37 -07:00
|
|
|
node.tNode = tData[index] as TNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
// Now link ourselves into the tree.
|
|
|
|
if (isParent) {
|
2018-01-29 14:51:37 +01:00
|
|
|
currentQueries = null;
|
2018-05-24 13:13:51 -07:00
|
|
|
if (previousOrParentNode.tNode.child == null && previousOrParentNode.view === currentView ||
|
2018-05-17 12:54:57 -07:00
|
|
|
previousOrParentNode.tNode.type === TNodeType.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.
|
2018-05-24 13:13:51 -07:00
|
|
|
previousOrParentNode.tNode.child = node.tNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-05-16 05:56:01 -07:00
|
|
|
|
|
|
|
// View nodes and host elements need to set their host node (components set host nodes later)
|
2018-05-17 12:54:57 -07:00
|
|
|
if ((type & TNodeType.ViewOrElement) === TNodeType.ViewOrElement && isState) {
|
2018-05-16 05:56:01 -07:00
|
|
|
// Bit of a hack to bust through the readonly because there is a circular dep between
|
|
|
|
// LView and LNode.
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode &&
|
|
|
|
assertNotDefined((state as LView).node, 'LView.node should not have been initialized');
|
2018-05-16 05:56:01 -07:00
|
|
|
(state as{node: LNode}).node = node;
|
|
|
|
if (firstTemplatePass) (state as LView).tView.node = node.tNode;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
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
|
|
|
/**
|
|
|
|
*
|
2018-03-25 21:32:39 -07:00
|
|
|
* @param hostNode Existing node to render into.
|
2017-12-01 14:23:03 -08:00
|
|
|
* @param template Template function with the instructions.
|
|
|
|
* @param context to pass into the template.
|
2018-03-25 21:32:39 -07:00
|
|
|
* @param providedRendererFactory renderer factory to use
|
|
|
|
* @param host The host element node to use
|
2018-03-29 12:58:41 -07:00
|
|
|
* @param directives Directive defs that should be used for matching
|
|
|
|
* @param pipes Pipe defs that should be used for matching
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2017-12-11 16:30:46 +01:00
|
|
|
export function renderTemplate<T>(
|
|
|
|
hostNode: RElement, template: ComponentTemplate<T>, context: T,
|
2018-03-25 21:32:39 -07:00
|
|
|
providedRendererFactory: RendererFactory3, host: LElementNode | null,
|
2018-05-09 15:30:16 -07:00
|
|
|
directives?: DirectiveDefListOrFactory | null, pipes?: PipeDefListOrFactory | null,
|
|
|
|
sanitizer?: Sanitizer | 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;
|
2018-03-27 15:53:48 -07:00
|
|
|
const tView = getOrCreateTView(template, directives || null, pipes || null);
|
2017-12-11 16:30:46 +01:00
|
|
|
host = createLNode(
|
2018-06-01 14:46:28 -07:00
|
|
|
-1, TNodeType.Element, hostNode, null, null,
|
2018-01-10 18:19:16 -08:00
|
|
|
createLView(
|
2018-06-04 13:07:09 -07:00
|
|
|
providedRendererFactory.createRenderer(null, null), tView, {}, LViewFlags.CheckAlways,
|
|
|
|
sanitizer));
|
2017-12-11 16:30:46 +01:00
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
const hostView = host.data !;
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(hostView, 'Host node should have an LView defined in host.data.');
|
2017-12-11 16:30:46 +01:00
|
|
|
renderComponentOrTemplate(host, hostView, context, template);
|
|
|
|
return host;
|
|
|
|
}
|
|
|
|
|
2018-04-26 10:44:49 -07:00
|
|
|
/**
|
|
|
|
* Used for rendering embedded views (e.g. dynamically created views)
|
|
|
|
*
|
|
|
|
* Dynamically created views must store/retrieve their TViews differently from component views
|
|
|
|
* because their template functions are nested in the template functions of their hosts, creating
|
|
|
|
* closures. If their host template happens to be an embedded template in a loop (e.g. ngFor inside
|
|
|
|
* an ngFor), the nesting would mean we'd have multiple instances of the template function, so we
|
|
|
|
* can't store TViews in the template function itself (as we do for comps). Instead, we store the
|
|
|
|
* TView for dynamically created views on their host TNode, which only has one instance.
|
|
|
|
*/
|
2018-01-17 10:09:05 -08:00
|
|
|
export function renderEmbeddedTemplate<T>(
|
2018-06-04 13:07:09 -07:00
|
|
|
viewNode: LViewNode | null, tView: TView, context: T, renderer: Renderer3,
|
|
|
|
queries?: LQueries | null): LViewNode {
|
2018-01-17 10:09:05 -08:00
|
|
|
const _isParent = isParent;
|
|
|
|
const _previousOrParentNode = previousOrParentNode;
|
2018-04-04 14:40:06 +02:00
|
|
|
let oldView: LView;
|
2018-04-12 13:49:37 +02:00
|
|
|
let rf: RenderFlags = RenderFlags.Update;
|
2018-01-17 10:09:05 -08:00
|
|
|
try {
|
|
|
|
isParent = true;
|
|
|
|
previousOrParentNode = null !;
|
2018-04-12 13:49:37 +02:00
|
|
|
|
2018-01-17 10:09:05 -08:00
|
|
|
if (viewNode == null) {
|
2018-06-04 13:07:09 -07:00
|
|
|
const lView =
|
|
|
|
createLView(renderer, tView, context, LViewFlags.CheckAlways, getCurrentSanitizer());
|
2018-04-03 10:18:25 +02:00
|
|
|
|
2018-05-28 11:57:36 +02:00
|
|
|
if (queries) {
|
|
|
|
lView.queries = queries.createView();
|
|
|
|
}
|
|
|
|
|
2018-06-01 14:46:28 -07:00
|
|
|
viewNode = createLNode(-1, TNodeType.View, null, null, null, lView);
|
2018-04-10 20:57:09 -07:00
|
|
|
rf = RenderFlags.Create;
|
2018-01-17 10:09:05 -08:00
|
|
|
}
|
2018-04-04 14:40:06 +02:00
|
|
|
oldView = enterView(viewNode.data, viewNode);
|
2018-06-04 13:07:09 -07:00
|
|
|
tView.template !(rf, context);
|
2018-04-12 13:49:37 +02:00
|
|
|
if (rf & RenderFlags.Update) {
|
|
|
|
refreshView();
|
|
|
|
} else {
|
|
|
|
viewNode.data.tView.firstTemplatePass = firstTemplatePass = false;
|
|
|
|
}
|
2018-03-13 11:48:09 -07:00
|
|
|
} finally {
|
2018-04-12 13:49:37 +02:00
|
|
|
// renderEmbeddedTemplate() is called twice in fact, once for creation only and then once for
|
|
|
|
// update. When for creation only, leaveView() must not trigger view hooks, nor clean flags.
|
|
|
|
const isCreationOnly = (rf & RenderFlags.Create) === RenderFlags.Create;
|
|
|
|
leaveView(oldView !, isCreationOnly);
|
2018-01-17 10:09:05 -08:00
|
|
|
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) {
|
2018-04-10 20:57:09 -07:00
|
|
|
template(getRenderFlags(hostView), componentOrContext !);
|
2018-04-12 13:49:37 +02:00
|
|
|
refreshView();
|
2017-12-11 16:30:46 +01:00
|
|
|
} else {
|
2018-03-13 11:48:09 -07:00
|
|
|
executeInitAndContentHooks();
|
2018-03-16 16:42:13 -07:00
|
|
|
|
2018-03-21 15:10:34 -07:00
|
|
|
// Element was stored at 0 in data and directive was stored at 0 in directives
|
|
|
|
// in renderComponent()
|
2018-03-25 21:32:39 -07:00
|
|
|
setHostBindings(_ROOT_DIRECTIVE_INDICES);
|
2018-03-21 15:10:34 -07:00
|
|
|
componentRefresh(0, 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-10 20:57:09 -07:00
|
|
|
/**
|
|
|
|
* This function returns the default configuration of rendering flags depending on when the
|
|
|
|
* template is in creation mode or update mode. By default, the update block is run with the
|
|
|
|
* creation block when the view is in creation mode. Otherwise, the update block is run
|
|
|
|
* alone.
|
|
|
|
*
|
|
|
|
* Dynamically created views do NOT use this configuration (update block and create block are
|
|
|
|
* always run separately).
|
|
|
|
*/
|
|
|
|
function getRenderFlags(view: LView): RenderFlags {
|
|
|
|
return view.flags & LViewFlags.CreationMode ? RenderFlags.Create | RenderFlags.Update :
|
|
|
|
RenderFlags.Update;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
//////////////////////////
|
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-03-25 21:32:39 -07:00
|
|
|
* @param name Name of the DOM Node
|
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 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-05-04 15:58:42 +02:00
|
|
|
index: number, name: string, attrs?: TAttributes | null,
|
|
|
|
localRefs?: string[] | null): RElement {
|
2018-03-27 11:01:52 -07:00
|
|
|
ngDevMode &&
|
2018-06-01 18:14:05 -07:00
|
|
|
assertEqual(currentView.bindingIndex, -1, 'elements should be created before any bindings');
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererCreateElement++;
|
2018-06-06 13:38:21 -07:00
|
|
|
const native: RElement = renderer.createElement(name);
|
2018-05-11 20:57:37 -07:00
|
|
|
ngDevMode && assertDataInRange(index - 1);
|
|
|
|
|
|
|
|
const node: LElementNode =
|
2018-05-17 12:54:57 -07:00
|
|
|
createLNode(index, TNodeType.Element, native !, name, attrs || null, null);
|
2018-03-21 15:10:34 -07:00
|
|
|
|
2018-03-27 11:01:52 -07:00
|
|
|
if (attrs) setUpAttributes(native, attrs);
|
2018-05-29 15:08:30 -07:00
|
|
|
appendChild(getParentLNode(node), native, currentView);
|
2018-05-04 15:58:42 +02:00
|
|
|
createDirectivesAndLocals(localRefs);
|
2018-04-04 21:21:12 -07:00
|
|
|
return native;
|
|
|
|
}
|
2018-03-21 15:10:34 -07:00
|
|
|
|
2018-04-26 10:44:49 -07:00
|
|
|
/**
|
|
|
|
* Creates directive instances and populates local refs.
|
|
|
|
*
|
|
|
|
* @param localRefs Local refs of the current node
|
|
|
|
*/
|
2018-05-04 15:58:42 +02:00
|
|
|
function createDirectivesAndLocals(localRefs?: string[] | null) {
|
2018-04-04 21:21:12 -07:00
|
|
|
const node = previousOrParentNode;
|
2018-05-04 15:58:42 +02:00
|
|
|
|
2018-03-27 11:01:52 -07:00
|
|
|
if (firstTemplatePass) {
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.firstTemplatePass++;
|
2018-05-16 05:56:01 -07:00
|
|
|
cacheMatchingDirectivesForNode(node.tNode, currentView.tView, localRefs || null);
|
2018-04-04 21:21:12 -07:00
|
|
|
} else {
|
|
|
|
instantiateDirectivesDirectly();
|
2018-03-25 21:32:39 -07:00
|
|
|
}
|
2018-04-04 21:21:12 -07:00
|
|
|
saveResolvedLocalsInData();
|
2018-03-25 21:32:39 -07:00
|
|
|
}
|
2017-12-11 14:08:52 -08:00
|
|
|
|
2018-04-04 21:21:12 -07:00
|
|
|
/**
|
|
|
|
* On first template pass, we match each node against available directive selectors and save
|
|
|
|
* the resulting defs in the correct instantiation order for subsequent change detection runs
|
|
|
|
* (so dependencies are always created before the directives that inject them).
|
|
|
|
*/
|
|
|
|
function cacheMatchingDirectivesForNode(
|
|
|
|
tNode: TNode, tView: TView, localRefs: string[] | null): void {
|
fix(ivy): workaround for tsickle bug (#23379)
The issue is with tsickle type inference and the bug should be assigned to them.
The offending code is:
```
function cacheMatchingDirectivesForNode(
tNode: TNode, tView: TView, localRefs: string[] | null): void {
const exportsMap = localRefs ? {'': -1} : null; // <<<<< ===== OFFENDING LINE
const matches = tView.currentMatches = findDirectiveMatches(tNode);
if (matches) {
for (let i = 0; i < matches.length; i += 2) {
const def = matches[i] as DirectiveDef<any>;
const valueIndex = i + 1;
resolveDirective(def, valueIndex, matches, tView);
saveNameToExportMap(matches[valueIndex] as number, def, exportsMap);
}
}
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
}
```
because it generates invalid js closure code:
```
function cacheMatchingDirectivesForNode(tNode, tView, localRefs) {
const /** @type {(null|{: number})} */ exportsMap = localRefs ? { '': -1 } : null; // <<<<< ===== OFFENDING LINE
const /** @type {(null|!Array<?>)} */ matches = tView.currentMatches = findDirectiveMatches(tNode);
if (matches) {
for (let /** @type {number} */ i = 0; i < matches.length; i += 2) {
const /** @type {!tsickle_forward_declare_11.DirectiveDef<?>} */ def = /** @type {!tsickle_forward_declare_11.DirectiveDef<?>} */ (matches[i]);
const /** @type {number} */ valueIndex = i + 1;
resolveDirective(def, valueIndex, matches, tView);
saveNameToExportMap(/** @type {number} */ (matches[valueIndex]), def, exportsMap);
}
}
if (exportsMap)
cacheMatchingLocalNames(tNode, localRefs, exportsMap);
}
```
The workaround is to declare the type explicitly such as:
```
const exportsMap: ({[key:string]:number}|null) = localRefs ? {'': -1} : null;
```
which than generates valid closure code:
```
const /** @type {(null|!Object<string,number>)} */ exportsMap = localRefs ? { '': -1 } : null;
```
PR Close #23379
2018-04-13 20:44:26 -07:00
|
|
|
// Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in tsickle.
|
|
|
|
const exportsMap: ({[key: string]: number} | null) = localRefs ? {'': -1} : null;
|
2018-04-04 21:21:12 -07:00
|
|
|
const matches = tView.currentMatches = findDirectiveMatches(tNode);
|
|
|
|
if (matches) {
|
|
|
|
for (let i = 0; i < matches.length; i += 2) {
|
2018-04-13 23:02:29 -07:00
|
|
|
const def = matches[i] as DirectiveDef<any>;
|
2018-04-04 21:21:12 -07:00
|
|
|
const valueIndex = i + 1;
|
|
|
|
resolveDirective(def, valueIndex, matches, tView);
|
|
|
|
saveNameToExportMap(matches[valueIndex] as number, def, exportsMap);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-04-04 21:21:12 -07:00
|
|
|
/** Matches the current node against all available selectors. */
|
|
|
|
function findDirectiveMatches(tNode: TNode): CurrentMatchesList|null {
|
|
|
|
const registry = currentView.tView.directiveRegistry;
|
|
|
|
let matches: any[]|null = null;
|
2018-03-25 21:32:39 -07:00
|
|
|
if (registry) {
|
|
|
|
for (let i = 0; i < registry.length; i++) {
|
|
|
|
const def = registry[i];
|
2018-03-29 16:41:45 -07:00
|
|
|
if (isNodeMatchingSelectorList(tNode, def.selectors !)) {
|
2018-04-13 23:02:29 -07:00
|
|
|
if ((def as ComponentDef<any>).template) {
|
2018-04-12 14:52:00 -07:00
|
|
|
if (tNode.flags & TNodeFlags.isComponent) throwMultipleComponentError(tNode);
|
|
|
|
tNode.flags = TNodeFlags.isComponent;
|
2018-03-25 21:32:39 -07:00
|
|
|
}
|
2018-04-04 21:21:12 -07:00
|
|
|
if (def.diPublic) def.diPublic(def);
|
|
|
|
(matches || (matches = [])).push(def, null);
|
2018-01-08 21:57:50 -08:00
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
2018-04-04 21:21:12 -07:00
|
|
|
return matches as CurrentMatchesList;
|
2018-03-25 21:32:39 -07:00
|
|
|
}
|
|
|
|
|
2018-04-04 21:21:12 -07:00
|
|
|
export function resolveDirective(
|
2018-04-13 23:02:29 -07:00
|
|
|
def: DirectiveDef<any>, valueIndex: number, matches: CurrentMatchesList, tView: TView): any {
|
2018-04-04 21:21:12 -07:00
|
|
|
if (matches[valueIndex] === null) {
|
|
|
|
matches[valueIndex] = CIRCULAR;
|
|
|
|
const instance = def.factory();
|
|
|
|
(tView.directives || (tView.directives = [])).push(def);
|
|
|
|
return directiveCreate(matches[valueIndex] = tView.directives !.length - 1, instance, def);
|
|
|
|
} else if (matches[valueIndex] === CIRCULAR) {
|
|
|
|
// If we revisit this directive before it's resolved, we know it's circular
|
|
|
|
throwCyclicDependencyError(def.type);
|
|
|
|
}
|
|
|
|
return null;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-03-16 16:42:13 -07:00
|
|
|
/** Stores index of component's host element so it will be queued for view refresh during CD. */
|
2018-04-04 21:21:12 -07:00
|
|
|
function queueComponentIndexForCheck(dirIndex: number): void {
|
2018-03-16 16:42:13 -07:00
|
|
|
if (firstTemplatePass) {
|
2018-04-04 21:21:12 -07:00
|
|
|
(currentView.tView.components || (currentView.tView.components = [
|
|
|
|
])).push(dirIndex, data.length - 1);
|
2018-03-16 16:42:13 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Stores index of directive and host element so it will be queued for binding refresh during CD.
|
|
|
|
*/
|
2018-04-04 21:21:12 -07:00
|
|
|
function queueHostBindingForCheck(dirIndex: number): void {
|
2018-03-21 15:10:34 -07:00
|
|
|
ngDevMode &&
|
|
|
|
assertEqual(firstTemplatePass, true, 'Should only be called in first template pass.');
|
2018-04-04 21:21:12 -07:00
|
|
|
(currentView.tView.hostBindings || (currentView.tView.hostBindings = [
|
|
|
|
])).push(dirIndex, data.length - 1);
|
2018-03-13 11:48:09 -07:00
|
|
|
}
|
|
|
|
|
2018-02-26 16:58:15 -08:00
|
|
|
/** Sets the context for a ChangeDetectorRef to the given instance. */
|
2018-03-25 21:32:39 -07:00
|
|
|
export function initChangeDetectorIfExisting(
|
|
|
|
injector: LInjector | null, instance: any, view: LView): void {
|
2018-02-26 16:58:15 -08:00
|
|
|
if (injector && injector.changeDetectorRef != null) {
|
2018-03-25 21:32:39 -07:00
|
|
|
(injector.changeDetectorRef as ViewRef<any>)._setComponentContext(view, instance);
|
2018-02-26 16:58:15 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-25 21:32:39 -07:00
|
|
|
export function isComponent(tNode: TNode): boolean {
|
2018-04-12 14:52:00 -07:00
|
|
|
return (tNode.flags & TNodeFlags.isComponent) === TNodeFlags.isComponent;
|
2018-03-25 21:32:39 -07:00
|
|
|
}
|
|
|
|
|
2018-01-08 21:57:50 -08:00
|
|
|
/**
|
2018-04-04 21:21:12 -07:00
|
|
|
* This function instantiates the given directives.
|
2018-01-08 21:57:50 -08:00
|
|
|
*/
|
2018-04-04 21:21:12 -07:00
|
|
|
function instantiateDirectivesDirectly() {
|
2018-05-16 05:56:01 -07:00
|
|
|
const tNode = previousOrParentNode.tNode;
|
2018-04-12 14:52:00 -07:00
|
|
|
const count = tNode.flags & TNodeFlags.DirectiveCountMask;
|
2018-03-27 11:01:52 -07:00
|
|
|
|
2018-04-12 14:52:00 -07:00
|
|
|
if (count > 0) {
|
|
|
|
const start = tNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
|
|
|
|
const end = start + count;
|
2018-03-25 21:32:39 -07:00
|
|
|
const tDirectives = currentView.tView.directives !;
|
2018-03-21 15:10:34 -07:00
|
|
|
|
2018-04-12 14:52:00 -07:00
|
|
|
for (let i = start; i < end; i++) {
|
2018-04-13 23:02:29 -07:00
|
|
|
const def: DirectiveDef<any> = tDirectives[i];
|
2018-04-04 21:21:12 -07:00
|
|
|
directiveCreate(i, def.factory(), def);
|
2018-01-08 21:57:50 -08:00
|
|
|
}
|
|
|
|
}
|
2018-03-27 11:01:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Caches local names and their matching directive indices for query and template lookups. */
|
|
|
|
function cacheMatchingLocalNames(
|
|
|
|
tNode: TNode, localRefs: string[] | null, exportsMap: {[key: string]: number}): void {
|
|
|
|
if (localRefs) {
|
|
|
|
const localNames: (string | number)[] = tNode.localNames = [];
|
|
|
|
|
|
|
|
// Local names must be stored in tNode in the same order that localRefs are defined
|
|
|
|
// in the template to ensure the data is loaded in the same slots as their refs
|
|
|
|
// in the template (for template queries).
|
|
|
|
for (let i = 0; i < localRefs.length; i += 2) {
|
2018-04-03 15:18:05 -07:00
|
|
|
const index = exportsMap[localRefs[i + 1]];
|
|
|
|
if (index == null) throw new Error(`Export of name '${localRefs[i + 1]}' not found!`);
|
2018-03-27 11:01:52 -07:00
|
|
|
localNames.push(localRefs[i], index);
|
|
|
|
}
|
|
|
|
}
|
2018-01-08 21:57:50 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-03-27 11:01:52 -07:00
|
|
|
* Builds up an export map as directives are created, so local refs can be quickly mapped
|
|
|
|
* to their directive instances.
|
2018-01-08 21:57:50 -08:00
|
|
|
*/
|
2018-03-27 11:01:52 -07:00
|
|
|
function saveNameToExportMap(
|
2018-04-13 23:02:29 -07:00
|
|
|
index: number, def: DirectiveDef<any>| ComponentDef<any>,
|
2018-03-27 11:01:52 -07:00
|
|
|
exportsMap: {[key: string]: number} | null) {
|
|
|
|
if (exportsMap) {
|
|
|
|
if (def.exportAs) exportsMap[def.exportAs] = index;
|
2018-04-13 23:02:29 -07:00
|
|
|
if ((def as ComponentDef<any>).template) exportsMap[''] = index;
|
2018-03-27 11:01:52 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Takes a list of local names and indices and pushes the resolved local variable values
|
|
|
|
* to data[] in the same order as they are loaded in the template with load().
|
|
|
|
*/
|
|
|
|
function saveResolvedLocalsInData(): void {
|
2018-05-16 05:56:01 -07:00
|
|
|
const localNames = previousOrParentNode.tNode.localNames;
|
2018-03-27 11:01:52 -07:00
|
|
|
if (localNames) {
|
|
|
|
for (let i = 0; i < localNames.length; i += 2) {
|
2018-04-03 15:18:05 -07:00
|
|
|
const index = localNames[i + 1] as number;
|
2018-03-27 11:01:52 -07:00
|
|
|
const value = index === -1 ? previousOrParentNode.native : directives ![index];
|
|
|
|
data.push(value);
|
2018-01-08 21:57:50 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-03-27 15:53:48 -07:00
|
|
|
* @param directives Directive defs that should be saved on TView
|
|
|
|
* @param pipes Pipe defs that should be saved on TView
|
2018-01-10 18:19:16 -08:00
|
|
|
* @returns TView
|
2017-12-11 14:08:52 -08:00
|
|
|
*/
|
2018-03-25 21:32:39 -07:00
|
|
|
function getOrCreateTView(
|
2018-03-27 15:53:48 -07:00
|
|
|
template: ComponentTemplate<any>, directives: DirectiveDefListOrFactory | null,
|
|
|
|
pipes: PipeDefListOrFactory | null): TView {
|
2018-04-26 10:44:49 -07:00
|
|
|
// TODO(misko): reading `ngPrivateData` here is problematic for two reasons
|
|
|
|
// 1. It is a megamorphic call on each invocation.
|
|
|
|
// 2. For nested embedded views (ngFor inside ngFor) the template instance is per
|
|
|
|
// outer template invocation, which means that no such property will exist
|
|
|
|
// Correct solution is to only put `ngPrivateData` on the Component template
|
|
|
|
// and not on embedded templates.
|
|
|
|
|
2018-03-27 15:53:48 -07:00
|
|
|
return template.ngPrivateData ||
|
2018-06-04 13:07:09 -07:00
|
|
|
(template.ngPrivateData = createTView(-1, template, directives, pipes) as never);
|
2018-01-22 17:43:52 -08:00
|
|
|
}
|
|
|
|
|
2018-06-01 19:28:20 -07:00
|
|
|
/**
|
|
|
|
* Creates a TView instance
|
|
|
|
*
|
|
|
|
* @param viewIndex The viewBlockId for inline views, or -1 if it's a component/dynamic
|
|
|
|
* @param directives Registry of directives for this view
|
|
|
|
* @param pipes Registry of pipes for this view
|
|
|
|
*/
|
2018-03-27 15:53:48 -07:00
|
|
|
export function createTView(
|
2018-06-04 13:07:09 -07:00
|
|
|
viewIndex: number, template: ComponentTemplate<any>| null,
|
|
|
|
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null): TView {
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.tView++;
|
2018-01-23 10:57:48 -08:00
|
|
|
return {
|
2018-06-01 19:28:20 -07:00
|
|
|
id: viewIndex,
|
2018-06-04 13:07:09 -07:00
|
|
|
template: template,
|
2018-05-16 05:56:01 -07:00
|
|
|
node: null !,
|
2018-01-23 10:57:48 -08:00
|
|
|
data: [],
|
2018-06-01 18:14:05 -07:00
|
|
|
childIndex: -1, // Children set in addToViewTree(), if any
|
|
|
|
bindingStartIndex: -1, // Set in initBindings()
|
2018-03-21 15:10:34 -07:00
|
|
|
directives: null,
|
2018-01-23 10:57:48 -08:00
|
|
|
firstTemplatePass: true,
|
|
|
|
initHooks: null,
|
2018-01-25 20:41:57 -08:00
|
|
|
checkHooks: null,
|
2018-01-23 10:57:48 -08:00
|
|
|
contentHooks: null,
|
2018-01-25 20:41:57 -08:00
|
|
|
contentCheckHooks: null,
|
2018-01-23 10:57:48 -08:00
|
|
|
viewHooks: null,
|
2018-01-25 20:41:57 -08:00
|
|
|
viewCheckHooks: null,
|
2018-03-13 11:48:09 -07:00
|
|
|
destroyHooks: null,
|
2018-03-21 15:10:34 -07:00
|
|
|
pipeDestroyHooks: null,
|
2018-06-05 15:28:15 -07:00
|
|
|
cleanup: null,
|
2018-03-16 16:42:13 -07:00
|
|
|
hostBindings: null,
|
2018-03-25 21:32:39 -07:00
|
|
|
components: null,
|
2018-06-01 19:28:20 -07:00
|
|
|
directiveRegistry: typeof directives === 'function' ? directives() : directives,
|
2018-04-04 21:21:12 -07:00
|
|
|
pipeRegistry: typeof pipes === 'function' ? pipes() : pipes,
|
|
|
|
currentMatches: null
|
2018-01-23 10:57:48 -08:00
|
|
|
};
|
2017-12-11 14:08:52 -08:00
|
|
|
}
|
|
|
|
|
2018-05-04 15:58:42 +02:00
|
|
|
function setUpAttributes(native: RElement, attrs: TAttributes): void {
|
2018-02-07 22:57:11 -08:00
|
|
|
const isProc = isProceduralRenderer(renderer);
|
2017-12-01 14:23:03 -08:00
|
|
|
for (let i = 0; i < attrs.length; i += 2) {
|
2018-06-06 13:38:20 -07:00
|
|
|
const attrName = attrs[i];
|
|
|
|
if (attrName === AttributeMarker.SELECT_ONLY) break;
|
|
|
|
if (attrName !== NG_PROJECT_AS_ATTR_NAME) {
|
|
|
|
const attrVal = attrs[i + 1];
|
|
|
|
ngDevMode && ngDevMode.rendererSetAttribute++;
|
|
|
|
isProc ?
|
|
|
|
(renderer as ProceduralRenderer3)
|
|
|
|
.setAttribute(native, attrName as string, attrVal as string) :
|
|
|
|
native.setAttribute(attrName as string, attrVal as string);
|
2018-02-28 15:00:58 +01:00
|
|
|
}
|
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' ?
|
2018-02-07 22:57:11 -08:00
|
|
|
(isProceduralRenderer(defaultRenderer) ?
|
|
|
|
defaultRenderer.selectRootElement(elementOrSelector) :
|
|
|
|
defaultRenderer.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-04-13 23:02:29 -07:00
|
|
|
* @param def ComponentDef
|
2018-02-26 16:58:15 -08:00
|
|
|
*
|
|
|
|
* @returns LElementNode created
|
2017-12-11 16:30:46 +01:00
|
|
|
*/
|
2018-03-25 21:32:39 -07:00
|
|
|
export function hostElement(
|
2018-05-09 15:30:16 -07:00
|
|
|
tag: string, rNode: RElement | null, def: ComponentDef<any>,
|
|
|
|
sanitizer?: Sanitizer | null): LElementNode {
|
2018-01-03 10:45:09 +01:00
|
|
|
resetApplicationState();
|
2018-03-20 19:06:49 -07:00
|
|
|
const node = createLNode(
|
2018-05-17 12:54:57 -07:00
|
|
|
0, TNodeType.Element, rNode, null, null,
|
2018-03-25 21:32:39 -07:00
|
|
|
createLView(
|
2018-06-04 13:07:09 -07:00
|
|
|
renderer, getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs), null,
|
2018-05-09 15:30:16 -07:00
|
|
|
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer));
|
2018-03-25 21:32:39 -07:00
|
|
|
|
|
|
|
if (firstTemplatePass) {
|
2018-05-16 05:56:01 -07:00
|
|
|
node.tNode.flags = TNodeFlags.isComponent;
|
2018-04-04 21:21:12 -07:00
|
|
|
if (def.diPublic) def.diPublic(def);
|
2018-03-25 21:32:39 -07:00
|
|
|
currentView.tView.directives = [def];
|
|
|
|
}
|
|
|
|
|
2018-03-20 19:06:49 -07:00
|
|
|
return node;
|
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
|
2018-03-01 09:46:39 -08:00
|
|
|
* @param listenerFn The function to be called when event emits
|
2017-12-01 14:23:03 -08:00
|
|
|
* @param useCapture Whether or not to use capture in event listener.
|
|
|
|
*/
|
2018-03-01 09:46:39 -08:00
|
|
|
export function listener(
|
|
|
|
eventName: string, listenerFn: (e?: any) => any, useCapture = false): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
ngDevMode && assertPreviousIsParent();
|
|
|
|
const node = previousOrParentNode;
|
|
|
|
const native = node.native as RElement;
|
2018-06-05 15:28:15 -07:00
|
|
|
ngDevMode && ngDevMode.rendererAddEventListener++;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
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).
|
2018-02-06 20:08:46 -08:00
|
|
|
if (isProceduralRenderer(renderer)) {
|
2018-03-01 09:46:39 -08:00
|
|
|
const wrappedListener = wrapListenerWithDirtyLogic(currentView, listenerFn);
|
2018-02-23 13:17:20 -08:00
|
|
|
const cleanupFn = renderer.listen(native, eventName, wrappedListener);
|
2018-06-05 15:28:15 -07:00
|
|
|
storeCleanupFn(currentView, cleanupFn);
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2018-03-01 09:46:39 -08:00
|
|
|
const wrappedListener = wrapListenerWithDirtyAndDefault(currentView, listenerFn);
|
2018-02-23 13:17:20 -08:00
|
|
|
native.addEventListener(eventName, wrappedListener, useCapture);
|
2018-06-05 15:28:15 -07:00
|
|
|
const cleanupInstances = getCleanup(currentView);
|
|
|
|
cleanupInstances.push(wrappedListener);
|
|
|
|
if (firstTemplatePass) {
|
|
|
|
getTViewCleanup(currentView)
|
|
|
|
.push(eventName, node.tNode.index, cleanupInstances !.length - 1, useCapture);
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-05-16 05:56:01 -07:00
|
|
|
let tNode: TNode|null = node.tNode;
|
2018-01-08 20:17:13 -08:00
|
|
|
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-05-16 05:56:01 -07:00
|
|
|
tNode.outputs = generatePropertyAliases(node.tNode.flags, BindingDirection.Output);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
const outputs = tNode.outputs;
|
2018-02-07 22:19:24 -08:00
|
|
|
let outputData: PropertyAliasValue|undefined;
|
2017-12-01 14:23:03 -08:00
|
|
|
if (outputs && (outputData = outputs[eventName])) {
|
2018-03-01 09:46:39 -08:00
|
|
|
createOutput(outputData, listenerFn);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterates through the outputs associated with a particular event name and subscribes to
|
|
|
|
* each output.
|
|
|
|
*/
|
2018-02-07 22:19:24 -08:00
|
|
|
function createOutput(outputs: PropertyAliasValue, listener: Function): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
for (let i = 0; i < outputs.length; i += 2) {
|
2018-03-21 15:10:34 -07:00
|
|
|
ngDevMode && assertDataInRange(outputs[i] as number, directives !);
|
2018-04-03 15:18:05 -07:00
|
|
|
const subscription = directives ![outputs[i] as number][outputs[i + 1]].subscribe(listener);
|
2018-06-05 15:28:15 -07:00
|
|
|
storeCleanupWithContext(currentView, subscription, subscription.unsubscribe);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Saves context for this cleanup function in LView.cleanupInstances.
|
|
|
|
*
|
|
|
|
* On the first template pass, saves in TView:
|
|
|
|
* - Cleanup function
|
|
|
|
* - Index of context we just saved in LView.cleanupInstances
|
|
|
|
*/
|
|
|
|
export function storeCleanupWithContext(
|
|
|
|
view: LView = currentView, context: any, cleanupFn: Function): void {
|
|
|
|
getCleanup(view).push(context);
|
|
|
|
|
|
|
|
if (view.tView.firstTemplatePass) {
|
|
|
|
getTViewCleanup(view).push(cleanupFn, view.cleanupInstances !.length - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Saves the cleanup function itself in LView.cleanupInstances.
|
|
|
|
*
|
|
|
|
* This is necessary for functions that are wrapped with their contexts, like in renderer2
|
|
|
|
* listeners.
|
|
|
|
*
|
|
|
|
* On the first template pass, the index of the cleanup function is saved in TView.
|
|
|
|
*/
|
|
|
|
export function storeCleanupFn(view: LView, cleanupFn: Function): void {
|
|
|
|
getCleanup(view).push(cleanupFn);
|
|
|
|
|
|
|
|
if (view.tView.firstTemplatePass) {
|
|
|
|
getTViewCleanup(view).push(view.cleanupInstances !.length - 1, null);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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();
|
2018-05-29 15:08:30 -07:00
|
|
|
previousOrParentNode = getParentLNode(previousOrParentNode) as LElementNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Element);
|
2018-01-29 14:51:37 +01:00
|
|
|
const queries = previousOrParentNode.queries;
|
|
|
|
queries && queries.addNode(previousOrParentNode);
|
2018-05-16 05:56:01 -07:00
|
|
|
queueLifecycleHooks(previousOrParentNode.tNode.flags, currentView);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-02-16 17:27:19 -08:00
|
|
|
* Updates the value of removes an attribute on an Element.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-02-16 17:27:19 -08:00
|
|
|
* @param number index The index of the element in the data array
|
2018-03-01 17:14:01 -08:00
|
|
|
* @param name name The name of the attribute.
|
|
|
|
* @param value value The attribute is removed when value is `null` or `undefined`.
|
2018-02-16 17:27:19 -08:00
|
|
|
* Otherwise the attribute value is set to the stringified value.
|
2018-03-01 17:14:01 -08:00
|
|
|
* @param sanitizer An optional function used to sanitize the value.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-03-01 17:14:01 -08:00
|
|
|
export function elementAttribute(
|
2018-05-09 15:30:16 -07:00
|
|
|
index: number, name: string, value: any, sanitizer?: SanitizerFn): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
if (value !== NO_CHANGE) {
|
2018-02-16 17:27:19 -08:00
|
|
|
const element: LElementNode = data[index];
|
2017-12-01 14:23:03 -08:00
|
|
|
if (value == null) {
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererRemoveAttribute++;
|
2018-02-16 17:27:19 -08:00
|
|
|
isProceduralRenderer(renderer) ? renderer.removeAttribute(element.native, name) :
|
|
|
|
element.native.removeAttribute(name);
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererSetAttribute++;
|
2018-03-01 17:14:01 -08:00
|
|
|
const strValue = sanitizer == null ? stringify(value) : sanitizer(value);
|
|
|
|
isProceduralRenderer(renderer) ? renderer.setAttribute(element.native, name, strValue) :
|
|
|
|
element.native.setAttribute(name, strValue);
|
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.
|
2018-03-01 17:14:01 -08:00
|
|
|
* @param sanitizer An optional function used to sanitize the value.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
|
|
|
|
2018-03-01 17:14:01 -08:00
|
|
|
export function elementProperty<T>(
|
2018-05-09 15:30:16 -07:00
|
|
|
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
if (value === NO_CHANGE) return;
|
2018-01-08 20:17:13 -08:00
|
|
|
const node = data[index] as LElementNode;
|
2018-05-16 05:56:01 -07:00
|
|
|
const tNode = node.tNode;
|
2018-01-08 20:17:13 -08:00
|
|
|
// 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-03-16 16:42:13 -07:00
|
|
|
if (tNode && tNode.inputs === undefined) {
|
2017-12-01 14:23:03 -08:00
|
|
|
// mark inputs as checked
|
2018-05-16 05:56:01 -07:00
|
|
|
tNode.inputs = generatePropertyAliases(node.tNode.flags, BindingDirection.Input);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-03-16 16:42:13 -07:00
|
|
|
const inputData = tNode && tNode.inputs;
|
2018-02-07 22:19:24 -08:00
|
|
|
let dataValue: PropertyAliasValue|undefined;
|
2017-12-01 14:23:03 -08:00
|
|
|
if (inputData && (dataValue = inputData[propName])) {
|
|
|
|
setInputsForProperty(dataValue, value);
|
2018-02-23 13:17:20 -08:00
|
|
|
markDirtyIfOnPush(node);
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2018-03-09 18:32:32 +01:00
|
|
|
// It is assumed that the sanitizer is only added when the compiler determines that the property
|
|
|
|
// is risky, so sanitization can be done without further checks.
|
|
|
|
value = sanitizer != null ? (sanitizer(value) as any) : value;
|
2017-12-01 14:23:03 -08:00
|
|
|
const native = node.native;
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererSetProperty++;
|
2018-02-07 22:57:11 -08:00
|
|
|
isProceduralRenderer(renderer) ? renderer.setProperty(native, propName, value) :
|
|
|
|
(native.setProperty ? native.setProperty(propName, value) :
|
|
|
|
(native as any)[propName] = value);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
*
|
2018-05-17 12:54:57 -07:00
|
|
|
* @param type The type of the node
|
2018-05-11 20:57:37 -07:00
|
|
|
* @param index The index of the TNode in TView.data
|
2018-04-26 10:44:49 -07:00
|
|
|
* @param tagName The tag name of the node
|
2018-05-11 20:57:37 -07:00
|
|
|
* @param attrs The attributes defined on this node
|
2018-05-29 15:08:30 -07:00
|
|
|
* @param parent The parent of this node
|
2018-04-26 10:44:49 -07:00
|
|
|
* @param tViews Any TViews attached to this node
|
2018-01-08 20:17:13 -08:00
|
|
|
* @returns the TNode object
|
2017-12-14 15:50:01 -08:00
|
|
|
*/
|
2018-05-16 05:56:01 -07:00
|
|
|
export function createTNode(
|
2018-06-01 14:46:28 -07:00
|
|
|
type: TNodeType, index: number, tagName: string | null, attrs: TAttributes | null,
|
2018-05-29 15:08:30 -07:00
|
|
|
parent: TElementNode | TContainerNode | null, tViews: TView[] | null): TNode {
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.tNode++;
|
2017-12-08 11:48:54 -08:00
|
|
|
return {
|
2018-05-17 12:54:57 -07:00
|
|
|
type: type,
|
2018-05-11 20:57:37 -07:00
|
|
|
index: index,
|
2018-03-20 19:06:49 -07:00
|
|
|
flags: 0,
|
2017-12-12 14:42:28 +01:00
|
|
|
tagName: tagName,
|
|
|
|
attrs: attrs,
|
2018-03-27 11:01:52 -07:00
|
|
|
localNames: null,
|
2017-12-08 11:48:54 -08:00
|
|
|
initialInputs: undefined,
|
|
|
|
inputs: undefined,
|
2017-12-11 14:08:52 -08:00
|
|
|
outputs: undefined,
|
2018-05-11 20:57:37 -07:00
|
|
|
tViews: tViews,
|
2018-05-16 05:56:01 -07:00
|
|
|
next: null,
|
2018-05-24 13:13:51 -07:00
|
|
|
child: null,
|
2018-05-29 15:08:30 -07:00
|
|
|
parent: parent,
|
2018-05-16 05:56:01 -07:00
|
|
|
dynamicContainerNode: null
|
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.
|
|
|
|
*/
|
2018-02-07 22:19:24 -08:00
|
|
|
function setInputsForProperty(inputs: PropertyAliasValue, value: any): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
for (let i = 0; i < inputs.length; i += 2) {
|
2018-03-21 15:10:34 -07:00
|
|
|
ngDevMode && assertDataInRange(inputs[i] as number, directives !);
|
2018-04-03 15:18:05 -07:00
|
|
|
directives ![inputs[i] as number][inputs[i + 1]] = value;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-02-07 22:19:24 -08:00
|
|
|
* Consolidates all inputs or outputs of all directives on this logical node.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-02-07 22:19:24 -08:00
|
|
|
* @param number lNodeFlags logical node flags
|
|
|
|
* @param Direction direction whether to consider inputs or outputs
|
|
|
|
* @returns PropertyAliases|null aggregate of all properties if any, `null` otherwise
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-03-20 19:06:49 -07:00
|
|
|
function generatePropertyAliases(
|
|
|
|
tNodeFlags: TNodeFlags, direction: BindingDirection): PropertyAliases|null {
|
2018-04-12 14:52:00 -07:00
|
|
|
const count = tNodeFlags & TNodeFlags.DirectiveCountMask;
|
2018-02-07 22:19:24 -08:00
|
|
|
let propStore: PropertyAliases|null = null;
|
|
|
|
|
2018-04-12 14:52:00 -07:00
|
|
|
if (count > 0) {
|
|
|
|
const start = tNodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
|
|
|
|
const end = start + count;
|
2018-02-07 22:19:24 -08:00
|
|
|
const isInput = direction === BindingDirection.Input;
|
2018-03-21 15:10:34 -07:00
|
|
|
const defs = currentView.tView.directives !;
|
2018-02-07 22:19:24 -08:00
|
|
|
|
2018-04-12 14:52:00 -07:00
|
|
|
for (let i = start; i < end; i++) {
|
2018-04-13 23:02:29 -07:00
|
|
|
const directiveDef = defs[i] as DirectiveDef<any>;
|
2018-02-07 22:19:24 -08:00
|
|
|
const propertyAliasMap: {[publicName: string]: string} =
|
|
|
|
isInput ? directiveDef.inputs : directiveDef.outputs;
|
|
|
|
for (let publicName in propertyAliasMap) {
|
|
|
|
if (propertyAliasMap.hasOwnProperty(publicName)) {
|
|
|
|
propStore = propStore || {};
|
|
|
|
const internalName = propertyAliasMap[publicName];
|
|
|
|
const hasProperty = propStore.hasOwnProperty(publicName);
|
|
|
|
hasProperty ? propStore[publicName].push(i, internalName) :
|
|
|
|
(propStore[publicName] = [i, internalName]);
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-02-07 22:19:24 -08:00
|
|
|
return propStore;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-03-08 13:57:56 -08:00
|
|
|
* Add or remove a class in a `classList` on a DOM element.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
2018-03-07 16:25:18 -08:00
|
|
|
export function elementClassNamed<T>(index: number, className: string, value: T | NO_CHANGE): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
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) {
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererAddClass++;
|
2018-02-07 22:57:11 -08:00
|
|
|
isProceduralRenderer(renderer) ? renderer.addClass(lElement.native, className) :
|
|
|
|
lElement.native.classList.add(className);
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
} else {
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererRemoveClass++;
|
2018-02-07 22:57:11 -08:00
|
|
|
isProceduralRenderer(renderer) ? renderer.removeClass(lElement.native, className) :
|
|
|
|
lElement.native.classList.remove(className);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-08 13:57:56 -08:00
|
|
|
/**
|
|
|
|
* Set the `className` property on a DOM element.
|
|
|
|
*
|
|
|
|
* This instruction is meant to handle the `[class]="exp"` usage.
|
|
|
|
*
|
|
|
|
* `elementClass` instruction writes the value to the "element's" `className` property.
|
|
|
|
*
|
|
|
|
* @param index The index of the element to update in the data array
|
|
|
|
* @param value A value indicating a set of classes which should be applied. The method overrides
|
|
|
|
* any existing classes. The value is stringified (`toString`) before it is applied to the
|
|
|
|
* element.
|
|
|
|
*/
|
|
|
|
export function elementClass<T>(index: number, value: T | NO_CHANGE): void {
|
|
|
|
if (value !== NO_CHANGE) {
|
|
|
|
// TODO: This is a naive implementation which simply writes value to the `className`. In the
|
|
|
|
// future
|
|
|
|
// we will add logic here which would work with the animation code.
|
|
|
|
const lElement: LElementNode = data[index];
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererSetClassName++;
|
2018-03-08 13:57:56 -08:00
|
|
|
isProceduralRenderer(renderer) ? renderer.setProperty(lElement.native, 'className', value) :
|
|
|
|
lElement.native['className'] = stringify(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
|
|
|
* 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).
|
2018-03-01 17:14:01 -08:00
|
|
|
* @param suffix Optional suffix. Used with scalar values to add unit such as `px`.
|
|
|
|
* @param sanitizer An optional function used to transform the value typically used for
|
|
|
|
* sanitization.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-03-07 16:25:18 -08:00
|
|
|
export function elementStyleNamed<T>(
|
2018-03-01 17:14:01 -08:00
|
|
|
index: number, styleName: string, value: T | NO_CHANGE, suffix?: string): void;
|
2018-03-07 16:25:18 -08:00
|
|
|
export function elementStyleNamed<T>(
|
2018-05-09 15:30:16 -07:00
|
|
|
index: number, styleName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void;
|
2018-03-07 16:25:18 -08:00
|
|
|
export function elementStyleNamed<T>(
|
2018-03-01 17:14:01 -08:00
|
|
|
index: number, styleName: string, value: T | NO_CHANGE,
|
2018-05-09 15:30:16 -07:00
|
|
|
suffixOrSanitizer?: string | SanitizerFn): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
if (value !== NO_CHANGE) {
|
2018-03-08 13:57:56 -08:00
|
|
|
const lElement: LElementNode = data[index];
|
2017-12-01 14:23:03 -08:00
|
|
|
if (value == null) {
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererRemoveStyle++;
|
2018-02-07 22:57:11 -08:00
|
|
|
isProceduralRenderer(renderer) ?
|
|
|
|
renderer.removeStyle(lElement.native, styleName, RendererStyleFlags3.DashCase) :
|
2018-03-08 13:57:56 -08:00
|
|
|
lElement.native['style'].removeProperty(styleName);
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2018-03-01 17:14:01 -08:00
|
|
|
let strValue =
|
|
|
|
typeof suffixOrSanitizer == 'function' ? suffixOrSanitizer(value) : stringify(value);
|
|
|
|
if (typeof suffixOrSanitizer == 'string') strValue = strValue + suffixOrSanitizer;
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererSetStyle++;
|
2018-02-07 22:57:11 -08:00
|
|
|
isProceduralRenderer(renderer) ?
|
|
|
|
renderer.setStyle(lElement.native, styleName, strValue, RendererStyleFlags3.DashCase) :
|
2018-03-08 13:57:56 -08:00
|
|
|
lElement.native['style'].setProperty(styleName, strValue);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-08 13:57:56 -08:00
|
|
|
/**
|
|
|
|
* Set the `style` property on a DOM element.
|
|
|
|
*
|
|
|
|
* This instruction is meant to handle the `[style]="exp"` usage.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @param index The index of the element to update in the data array
|
|
|
|
* @param value A value indicating if a given style should be added or removed.
|
|
|
|
* The expected shape of `value` is an object where keys are style names and the values
|
2018-06-01 14:46:28 -07:00
|
|
|
* are their corresponding values to set. If value is falsy, then the style is removed. An absence
|
2018-03-08 13:57:56 -08:00
|
|
|
* of style does not cause that style to be removed. `NO_CHANGE` implies that no update should be
|
|
|
|
* performed.
|
|
|
|
*/
|
|
|
|
export function elementStyle<T>(
|
|
|
|
index: number, value: {[styleName: string]: any} | NO_CHANGE): void {
|
|
|
|
if (value !== NO_CHANGE) {
|
|
|
|
// TODO: This is a naive implementation which simply writes value to the `style`. In the future
|
|
|
|
// we will add logic here which would work with the animation code.
|
|
|
|
const lElement = data[index] as LElementNode;
|
|
|
|
if (isProceduralRenderer(renderer)) {
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererSetStyle++;
|
2018-03-08 13:57:56 -08:00
|
|
|
renderer.setProperty(lElement.native, 'style', value);
|
|
|
|
} else {
|
|
|
|
const style = lElement.native['style'];
|
|
|
|
for (let i = 0, keys = Object.keys(value); i < keys.length; i++) {
|
|
|
|
const styleName: string = keys[i];
|
|
|
|
const styleValue: any = (value as any)[styleName];
|
2018-04-14 11:52:53 -07:00
|
|
|
if (styleValue == null) {
|
|
|
|
ngDevMode && ngDevMode.rendererRemoveStyle++;
|
|
|
|
style.removeProperty(styleName);
|
|
|
|
} else {
|
|
|
|
ngDevMode && ngDevMode.rendererSetStyle++;
|
|
|
|
style.setProperty(styleName, styleValue);
|
|
|
|
}
|
2018-03-08 13:57:56 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
//////////////////////////
|
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.
|
|
|
|
*/
|
2017-12-14 16:26:28 -08:00
|
|
|
export function text(index: number, value?: any): void {
|
2018-02-12 22:46:15 -08:00
|
|
|
ngDevMode &&
|
2018-06-01 18:14:05 -07:00
|
|
|
assertEqual(currentView.bindingIndex, -1, 'text nodes should be created before bindings');
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererCreateTextNode++;
|
2018-04-14 12:20:45 -07:00
|
|
|
const textNode = createTextNode(value, renderer);
|
2018-05-17 12:54:57 -07:00
|
|
|
const node = createLNode(index, TNodeType.Element, textNode, null, null);
|
2018-05-11 20:57:37 -07:00
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
// Text nodes are self closing.
|
|
|
|
isParent = false;
|
2018-05-29 15:08:30 -07:00
|
|
|
appendChild(getParentLNode(node), textNode, currentView);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create text node with binding
|
2018-05-13 21:01:37 +02:00
|
|
|
* Bindings should be handled externally with the proper interpolation(1-8) method
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
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-05-15 22:07:20 +02:00
|
|
|
if (value !== NO_CHANGE) {
|
|
|
|
ngDevMode && assertDataInRange(index);
|
|
|
|
const existingNode = data[index] as LTextNode;
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(existingNode, 'LNode should exist');
|
|
|
|
ngDevMode && assertDefined(existingNode.native, 'native element should exist');
|
2018-05-15 22:07:20 +02:00
|
|
|
ngDevMode && ngDevMode.rendererSetText++;
|
|
|
|
isProceduralRenderer(renderer) ? renderer.setValue(existingNode.native, stringify(value)) :
|
|
|
|
existingNode.native.textContent = stringify(value);
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////
|
|
|
|
//// 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 directive The directive instance.
|
2018-04-13 23:02:29 -07:00
|
|
|
* @param directiveDef DirectiveDef object which contains information about the template.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-01-08 21:57:50 -08:00
|
|
|
export function directiveCreate<T>(
|
2018-04-13 23:02:29 -07:00
|
|
|
index: number, directive: T, directiveDef: DirectiveDef<T>| ComponentDef<T>): T {
|
2018-03-16 20:31:24 -07:00
|
|
|
const instance = baseDirectiveCreate(index, directive, directiveDef);
|
|
|
|
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(previousOrParentNode.tNode, 'previousOrParentNode.tNode');
|
2018-04-12 14:52:00 -07:00
|
|
|
const tNode = previousOrParentNode.tNode;
|
2018-03-16 20:31:24 -07:00
|
|
|
|
2018-04-13 23:02:29 -07:00
|
|
|
const isComponent = (directiveDef as ComponentDef<T>).template;
|
2018-03-25 21:32:39 -07:00
|
|
|
if (isComponent) {
|
2018-04-13 23:02:29 -07:00
|
|
|
addComponentLogic(index, directive, directiveDef as ComponentDef<T>);
|
2018-03-25 21:32:39 -07:00
|
|
|
}
|
|
|
|
|
2018-03-21 15:10:34 -07:00
|
|
|
if (firstTemplatePass) {
|
|
|
|
// Init hooks are queued now so ngOnInit is called in host components before
|
|
|
|
// any projected components.
|
|
|
|
queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, currentView.tView);
|
|
|
|
|
2018-04-04 21:21:12 -07:00
|
|
|
if (directiveDef.hostBindings) queueHostBindingForCheck(index);
|
2018-03-16 20:31:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (tNode && tNode.attrs) {
|
2018-04-04 09:22:53 -07:00
|
|
|
setInputsFromAttrs(index, instance, directiveDef.inputs, tNode);
|
2018-03-16 20:31:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
2018-04-13 23:02:29 -07:00
|
|
|
function addComponentLogic<T>(index: number, instance: T, def: ComponentDef<T>): void {
|
2018-03-27 15:53:48 -07:00
|
|
|
const tView = getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs);
|
2018-03-25 21:32:39 -07: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.
|
2018-03-15 17:33:35 +01:00
|
|
|
const hostView = addToViewTree(
|
2018-05-30 13:43:14 -07:00
|
|
|
currentView, previousOrParentNode.tNode.index as number,
|
|
|
|
createLView(
|
|
|
|
rendererFactory.createRenderer(previousOrParentNode.native as RElement, def.rendererType),
|
2018-06-04 13:07:09 -07:00
|
|
|
tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways,
|
2018-05-30 13:43:14 -07:00
|
|
|
getCurrentSanitizer()));
|
2018-03-25 21:32:39 -07:00
|
|
|
|
2018-05-16 05:56:01 -07:00
|
|
|
// We need to set the host node/data here because when the component LNode was created,
|
|
|
|
// we didn't yet know it was a component (just an element).
|
|
|
|
(previousOrParentNode as{data: LView}).data = hostView;
|
|
|
|
(hostView as{node: LNode}).node = previousOrParentNode;
|
|
|
|
if (firstTemplatePass) tView.node = previousOrParentNode.tNode;
|
2018-03-25 21:32:39 -07:00
|
|
|
|
|
|
|
initChangeDetectorIfExisting(previousOrParentNode.nodeInjector, instance, hostView);
|
|
|
|
|
2018-04-04 21:21:12 -07:00
|
|
|
if (firstTemplatePass) queueComponentIndexForCheck(index);
|
2018-03-25 21:32:39 -07:00
|
|
|
}
|
|
|
|
|
2018-03-16 20:31:24 -07:00
|
|
|
/**
|
|
|
|
* A lighter version of directiveCreate() that is used for the root component
|
|
|
|
*
|
|
|
|
* This version does not contain features that we don't already support at root in
|
|
|
|
* current Angular. Example: local refs and inputs on root component.
|
|
|
|
*/
|
|
|
|
export function baseDirectiveCreate<T>(
|
2018-04-13 23:02:29 -07:00
|
|
|
index: number, directive: T, directiveDef: DirectiveDef<T>| ComponentDef<T>): T {
|
2018-02-12 22:46:15 -08:00
|
|
|
ngDevMode &&
|
2018-06-01 18:14:05 -07:00
|
|
|
assertEqual(currentView.bindingIndex, -1, 'directives should be created before any bindings');
|
2018-01-08 21:57:50 -08:00
|
|
|
ngDevMode && assertPreviousIsParent();
|
2018-03-20 19:06:49 -07:00
|
|
|
|
2018-01-08 21:57:50 -08:00
|
|
|
Object.defineProperty(
|
|
|
|
directive, NG_HOST_SYMBOL, {enumerable: false, value: previousOrParentNode});
|
2017-12-19 16:51:42 +01:00
|
|
|
|
2018-03-21 15:10:34 -07:00
|
|
|
if (directives == null) currentView.directives = directives = [];
|
2017-12-11 14:08:52 -08:00
|
|
|
|
2018-03-21 15:10:34 -07:00
|
|
|
ngDevMode && assertDataNext(index, directives);
|
|
|
|
directives[index] = directive;
|
|
|
|
|
2018-04-04 21:21:12 -07:00
|
|
|
if (firstTemplatePass) {
|
2018-05-16 05:56:01 -07:00
|
|
|
const flags = previousOrParentNode.tNode.flags;
|
2018-04-12 14:52:00 -07:00
|
|
|
if ((flags & TNodeFlags.DirectiveCountMask) === 0) {
|
|
|
|
// When the first directive is created:
|
|
|
|
// - save the index,
|
|
|
|
// - set the number of directives to 1
|
2018-05-16 05:56:01 -07:00
|
|
|
previousOrParentNode.tNode.flags =
|
2018-04-12 14:52:00 -07:00
|
|
|
index << TNodeFlags.DirectiveStartingIndexShift | flags & TNodeFlags.isComponent | 1;
|
|
|
|
} else {
|
|
|
|
// Only need to bump the size when subsequent directives are created
|
|
|
|
ngDevMode && assertNotEqual(
|
|
|
|
flags & TNodeFlags.DirectiveCountMask, TNodeFlags.DirectiveCountMask,
|
|
|
|
'Reached the max number of directives');
|
2018-05-16 05:56:01 -07:00
|
|
|
previousOrParentNode.tNode.flags++;
|
2018-04-12 14:52:00 -07:00
|
|
|
}
|
2018-04-04 21:21:12 -07:00
|
|
|
} else {
|
|
|
|
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-05-17 12:54:57 -07:00
|
|
|
if (directiveDef !.attributes != null && previousOrParentNode.tNode.type == TNodeType.Element) {
|
2018-02-16 12:09:47 -08:00
|
|
|
setUpAttributes(
|
|
|
|
(previousOrParentNode as LElementNode).native, directiveDef !.attributes as string[]);
|
|
|
|
}
|
|
|
|
|
2018-03-21 15:10:34 -07:00
|
|
|
return directive;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets initial input properties on directive instances from attribute data
|
|
|
|
*
|
2018-03-21 15:10:34 -07:00
|
|
|
* @param directiveIndex Index of the directive in directives array
|
2017-12-01 14:23:03 -08:00
|
|
|
* @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-03-21 15:10:34 -07:00
|
|
|
function setInputsFromAttrs<T>(
|
|
|
|
directiveIndex: number, instance: T, inputs: {[key: string]: string}, tNode: TNode): void {
|
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) {
|
2018-04-03 15:18:05 -07:00
|
|
|
(instance as any)[initialInputs[i]] = initialInputs[i + 1];
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
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) {
|
2018-06-06 13:38:20 -07:00
|
|
|
const attrName = attrs[i];
|
2017-12-01 14:23:03 -08:00
|
|
|
const minifiedInputName = inputs[attrName];
|
2018-05-04 15:58:42 +02:00
|
|
|
const attrValue = attrs[i + 1];
|
|
|
|
|
2018-06-06 13:38:00 -07:00
|
|
|
if (attrName === AttributeMarker.SELECT_ONLY) break;
|
2017-12-01 14:23:03 -08:00
|
|
|
if (minifiedInputName !== undefined) {
|
|
|
|
const inputsToStore: InitialInputs =
|
|
|
|
initialInputData[directiveIndex] || (initialInputData[directiveIndex] = []);
|
2018-05-04 15:58:42 +02:00
|
|
|
inputsToStore.push(minifiedInputName, attrValue as string);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return initialInputData;
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////
|
|
|
|
//// ViewContainer & View
|
|
|
|
//////////////////////////
|
|
|
|
|
2018-05-17 16:19:44 +02:00
|
|
|
/**
|
|
|
|
* Creates a LContainer, either from a container instruction, or for a ViewContainerRef.
|
|
|
|
*
|
|
|
|
* @param parentLNode the LNode in which the container's content will be rendered
|
|
|
|
* @param currentView The parent view of the LContainer
|
|
|
|
* @param template Optional the inline template (ng-template instruction case)
|
|
|
|
* @param isForViewContainerRef Optional a flag indicating the ViewContainerRef case
|
|
|
|
* @returns LContainer
|
|
|
|
*/
|
2018-03-15 17:33:35 +01:00
|
|
|
export function createLContainer(
|
2018-05-17 16:19:44 +02:00
|
|
|
parentLNode: LNode, currentView: LView, template?: ComponentTemplate<any>,
|
|
|
|
isForViewContainerRef?: boolean): LContainer {
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(parentLNode, 'containers should have a parent');
|
2018-03-15 17:33:35 +01:00
|
|
|
return <LContainer>{
|
|
|
|
views: [],
|
2018-05-17 16:19:44 +02:00
|
|
|
nextIndex: isForViewContainerRef ? null : 0,
|
2018-03-15 17:33:35 +01:00
|
|
|
// If the direct parent of the container is a view, its views will need to be added
|
|
|
|
// through insertView() when its parent view is being inserted:
|
|
|
|
renderParent: canInsertNativeNode(parentLNode, currentView) ? parentLNode : null,
|
|
|
|
template: template == null ? null : template,
|
|
|
|
next: null,
|
|
|
|
parent: currentView,
|
2018-04-03 10:18:25 +02:00
|
|
|
queries: null
|
2018-03-15 17:33:35 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
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-05-04 15:58:42 +02:00
|
|
|
index: number, template?: ComponentTemplate<any>, tagName?: string | null, attrs?: TAttributes,
|
2018-03-25 21:32:39 -07:00
|
|
|
localRefs?: string[] | null): void {
|
2018-06-01 18:14:05 -07:00
|
|
|
ngDevMode &&
|
|
|
|
assertEqual(
|
|
|
|
currentView.bindingIndex, -1, 'container nodes should be created before any bindings');
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-05-29 15:08:30 -07:00
|
|
|
const currentParent = isParent ? previousOrParentNode : getParentLNode(previousOrParentNode) !;
|
2018-03-15 17:33:35 +01:00
|
|
|
const lContainer = createLContainer(currentParent, currentView, template);
|
2018-01-17 17:55:55 +01:00
|
|
|
|
2018-05-11 20:57:37 -07:00
|
|
|
const node = createLNode(
|
2018-05-17 12:54:57 -07:00
|
|
|
index, TNodeType.Container, undefined, tagName || null, attrs || null, lContainer);
|
2018-05-11 20:57:37 -07:00
|
|
|
|
2018-05-16 05:56:01 -07:00
|
|
|
if (firstTemplatePass && template == null) node.tNode.tViews = [];
|
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.
|
2018-05-30 13:43:14 -07:00
|
|
|
addToViewTree(currentView, index, node.data);
|
2018-06-01 17:01:24 +02:00
|
|
|
|
|
|
|
const queries = node.queries;
|
|
|
|
if (queries) {
|
|
|
|
// prepare place for matching nodes from views inserted into a given container
|
|
|
|
lContainer.queries = queries.container();
|
|
|
|
}
|
|
|
|
|
2018-05-04 15:58:42 +02:00
|
|
|
createDirectivesAndLocals(localRefs);
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-01-09 13:32:24 -08:00
|
|
|
isParent = false;
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container);
|
2018-01-29 14:51:37 +01:00
|
|
|
if (queries) {
|
2018-01-17 17:55:55 +01:00
|
|
|
// check if a given container node matches
|
2018-01-29 14:51:37 +01:00
|
|
|
queries.addNode(node);
|
2018-01-17 17:55:55 +01:00
|
|
|
}
|
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;
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container);
|
2017-12-01 14:23:03 -08:00
|
|
|
isParent = true;
|
2018-01-26 11:36:31 +01:00
|
|
|
(previousOrParentNode as LContainerNode).data.nextIndex = 0;
|
2018-02-12 22:46:15 -08:00
|
|
|
ngDevMode && assertSame(
|
|
|
|
(previousOrParentNode as LContainerNode).native, undefined,
|
|
|
|
`the container's native element should not have been set yet.`);
|
2018-01-22 17:43:52 -08:00
|
|
|
|
2018-03-09 20:22:18 -08:00
|
|
|
if (!checkNoChangesMode) {
|
|
|
|
// 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, currentView.tView, creationMode);
|
|
|
|
}
|
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 {
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View);
|
2017-12-01 14:23:03 -08:00
|
|
|
ngDevMode && assertHasParent();
|
2018-05-29 15:08:30 -07:00
|
|
|
previousOrParentNode = getParentLNode(previousOrParentNode) !;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container);
|
2018-01-08 20:17:13 -08:00
|
|
|
const container = previousOrParentNode as LContainerNode;
|
2018-01-26 11:36:31 +01:00
|
|
|
container.native = undefined;
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(container, TNodeType.Container);
|
2018-05-17 16:19:44 +02:00
|
|
|
const nextIndex = container.data.nextIndex !;
|
2018-03-08 12:10:20 +01:00
|
|
|
|
|
|
|
// remove extra views at the end of the container
|
2017-12-13 19:34:46 -08:00
|
|
|
while (nextIndex < container.data.views.length) {
|
2017-12-01 14:23:03 -08:00
|
|
|
removeView(container, nextIndex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-17 10:09:05 -08:00
|
|
|
function refreshDynamicChildren() {
|
2018-05-30 13:43:14 -07:00
|
|
|
for (let current = getLViewChild(currentView); current !== null; current = current.next) {
|
2018-05-17 16:19:44 +02:00
|
|
|
// Note: current can be a LView or a LContainer, but here we are only interested in LContainer.
|
|
|
|
// The distinction is made because nextIndex and views do not exist on LView.
|
|
|
|
if (isLContainer(current)) {
|
2018-01-17 10:09:05 -08:00
|
|
|
const container = current as LContainer;
|
|
|
|
for (let i = 0; i < container.views.length; i++) {
|
2018-04-26 10:44:49 -07:00
|
|
|
const lViewNode = container.views[i];
|
2018-04-11 14:15:30 +02:00
|
|
|
// The directives and pipes are not needed here as an existing view is only being refreshed.
|
2018-04-26 10:44:49 -07:00
|
|
|
const dynamicView = lViewNode.data;
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(dynamicView.tView, 'TView must be allocated');
|
2018-06-04 13:07:09 -07:00
|
|
|
renderEmbeddedTemplate(lViewNode, dynamicView.tView, dynamicView.context !, renderer);
|
2018-01-17 10:09:05 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-17 16:19:44 +02:00
|
|
|
function isLContainer(node: LView | LContainer): node is LContainer {
|
|
|
|
return (node as LContainer).nextIndex == null && (node as LContainer).views != null;
|
|
|
|
}
|
|
|
|
|
2018-03-08 12:10:20 +01:00
|
|
|
/**
|
|
|
|
* Looks for a view with a given view block id inside a provided LContainer.
|
|
|
|
* Removes views that need to be deleted in the process.
|
|
|
|
*
|
|
|
|
* @param containerNode where to search for views
|
|
|
|
* @param startIdx starting index in the views array to search from
|
|
|
|
* @param viewBlockId exact view block id to look for
|
|
|
|
* @returns index of a found view or -1 if not found
|
|
|
|
*/
|
|
|
|
function scanForView(
|
|
|
|
containerNode: LContainerNode, startIdx: number, viewBlockId: number): LViewNode|null {
|
|
|
|
const views = containerNode.data.views;
|
|
|
|
for (let i = startIdx; i < views.length; i++) {
|
2018-06-01 19:28:20 -07:00
|
|
|
const viewAtPositionId = views[i].data.tView.id;
|
2018-03-08 12:10:20 +01:00
|
|
|
if (viewAtPositionId === viewBlockId) {
|
|
|
|
return views[i];
|
|
|
|
} else if (viewAtPositionId < viewBlockId) {
|
|
|
|
// found a view that should not be at this position - remove
|
|
|
|
removeView(containerNode, i);
|
|
|
|
} else {
|
2018-06-01 14:46:28 -07:00
|
|
|
// found a view with id greater than the one we are searching for
|
2018-03-08 12:10:20 +01:00
|
|
|
// which means that required view doesn't exist and can't be found at
|
|
|
|
// later positions in the views array - stop the search here
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
2018-02-06 17:27:16 -08:00
|
|
|
* Marks the start of an embedded view.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
|
|
|
* @param viewBlockId The ID of this view
|
2018-02-06 17:27:16 -08:00
|
|
|
* @return boolean Whether or not this view is in creation mode
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-04-10 20:57:09 -07:00
|
|
|
export function embeddedViewStart(viewBlockId: number): RenderFlags {
|
2018-01-08 20:17:13 -08:00
|
|
|
const container =
|
2018-05-29 15:08:30 -07:00
|
|
|
(isParent ? previousOrParentNode : getParentLNode(previousOrParentNode)) as LContainerNode;
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(container, TNodeType.Container);
|
2018-01-08 20:17:13 -08:00
|
|
|
const lContainer = container.data;
|
2018-05-17 16:19:44 +02:00
|
|
|
let viewNode: LViewNode|null = scanForView(container, lContainer.nextIndex !, viewBlockId);
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-04-10 20:57:09 -07:00
|
|
|
if (viewNode) {
|
|
|
|
previousOrParentNode = viewNode;
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View);
|
2017-12-01 14:23:03 -08:00
|
|
|
isParent = true;
|
2018-04-10 20:57:09 -07:00
|
|
|
enterView(viewNode.data, viewNode);
|
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-02-03 20:34:30 -08:00
|
|
|
const newView = createLView(
|
2018-06-04 13:07:09 -07:00
|
|
|
renderer, getOrCreateEmbeddedTView(viewBlockId, container), null, LViewFlags.CheckAlways,
|
|
|
|
getCurrentSanitizer());
|
2018-05-28 11:57:36 +02:00
|
|
|
|
2018-01-29 14:51:37 +01:00
|
|
|
if (lContainer.queries) {
|
2018-05-28 11:57:36 +02:00
|
|
|
newView.queries = lContainer.queries.createView();
|
2018-01-17 17:55:55 +01:00
|
|
|
}
|
|
|
|
|
2018-05-16 05:56:01 -07:00
|
|
|
enterView(
|
2018-05-17 12:54:57 -07:00
|
|
|
newView, viewNode = createLNode(viewBlockId, TNodeType.View, null, null, null, newView));
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-04-10 20:57:09 -07:00
|
|
|
return getRenderFlags(viewNode.data);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
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-04-26 10:44:49 -07:00
|
|
|
* Each embedded view block must create or retrieve its own TView. Otherwise, the embedded view's
|
|
|
|
* static data for a particular node would overwrite the static data for a node in the view above
|
|
|
|
* it with the same index (since it's in the same template).
|
2017-12-08 11:48:54 -08:00
|
|
|
*
|
2018-04-26 10:44:49 -07:00
|
|
|
* @param viewIndex The index of the TView in TNode.tViews
|
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 {
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(parent, TNodeType.Container);
|
2018-04-26 10:44:49 -07:00
|
|
|
const containerTViews = (parent !.tNode as TContainerNode).tViews as TView[];
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(containerTViews, 'TView expected');
|
2018-04-26 10:44:49 -07:00
|
|
|
ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array');
|
|
|
|
if (viewIndex >= containerTViews.length || containerTViews[viewIndex] == null) {
|
2018-03-27 15:53:48 -07:00
|
|
|
const tView = currentView.tView;
|
2018-06-01 19:28:20 -07:00
|
|
|
containerTViews[viewIndex] =
|
2018-06-04 13:07:09 -07:00
|
|
|
createTView(viewIndex, null, tView.directiveRegistry, tView.pipeRegistry);
|
2017-12-08 11:48:54 -08:00
|
|
|
}
|
2018-04-26 10:44:49 -07:00
|
|
|
return containerTViews[viewIndex];
|
2017-12-08 11:48:54 -08:00
|
|
|
}
|
|
|
|
|
2018-02-06 17:27:16 -08:00
|
|
|
/** Marks the end of an embedded view. */
|
|
|
|
export function embeddedViewEnd(): void {
|
2018-04-12 13:49:37 +02:00
|
|
|
refreshView();
|
2017-12-01 14:23:03 -08:00
|
|
|
isParent = false;
|
2018-01-08 20:17:13 -08:00
|
|
|
const viewNode = previousOrParentNode = currentView.node as LViewNode;
|
2018-05-29 15:08:30 -07:00
|
|
|
const containerNode = getParentLNode(previousOrParentNode) as LContainerNode;
|
2018-03-08 12:10:20 +01:00
|
|
|
if (containerNode) {
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(viewNode, TNodeType.View);
|
|
|
|
ngDevMode && assertNodeType(containerNode, TNodeType.Container);
|
2018-03-08 12:10:20 +01:00
|
|
|
const lContainer = containerNode.data;
|
|
|
|
|
|
|
|
if (creationMode) {
|
2018-04-12 12:13:39 +02:00
|
|
|
// When projected nodes are going to be inserted, the renderParent of the dynamic container
|
|
|
|
// used by the ViewContainerRef must be set.
|
|
|
|
setRenderParentInProjectedNodes(lContainer.renderParent, viewNode);
|
2018-03-08 12:10:20 +01:00
|
|
|
// it is a new view, insert it into collection of views for a given container
|
2018-05-17 16:19:44 +02:00
|
|
|
insertView(containerNode, viewNode, lContainer.nextIndex !);
|
2018-01-17 10:09:05 -08:00
|
|
|
}
|
2018-03-08 12:10:20 +01:00
|
|
|
|
2018-05-17 16:19:44 +02:00
|
|
|
lContainer.nextIndex !++;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
leaveView(currentView !.parent !);
|
|
|
|
ngDevMode && assertEqual(isParent, false, 'isParent');
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-01-17 10:09:05 -08:00
|
|
|
|
2018-04-12 12:13:39 +02:00
|
|
|
/**
|
|
|
|
* For nodes which are projected inside an embedded view, this function sets the renderParent
|
|
|
|
* of their dynamic LContainerNode.
|
|
|
|
* @param renderParent the renderParent of the LContainer which contains the embedded view.
|
|
|
|
* @param viewNode the embedded view.
|
|
|
|
*/
|
|
|
|
function setRenderParentInProjectedNodes(
|
|
|
|
renderParent: LElementNode | null, viewNode: LViewNode): void {
|
|
|
|
if (renderParent != null) {
|
2018-05-24 13:13:51 -07:00
|
|
|
let node: LNode|null = getChildLNode(viewNode);
|
2018-04-12 12:13:39 +02:00
|
|
|
while (node) {
|
2018-05-17 12:54:57 -07:00
|
|
|
if (node.tNode.type === TNodeType.Projection) {
|
2018-04-12 12:13:39 +02:00
|
|
|
let nodeToProject: LNode|null = (node as LProjectionNode).data.head;
|
|
|
|
const lastNodeToProject = (node as LProjectionNode).data.tail;
|
|
|
|
while (nodeToProject) {
|
|
|
|
if (nodeToProject.dynamicLContainerNode) {
|
|
|
|
nodeToProject.dynamicLContainerNode.data.renderParent = renderParent;
|
|
|
|
}
|
|
|
|
nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent;
|
|
|
|
}
|
|
|
|
}
|
2018-05-11 20:57:37 -07:00
|
|
|
node = getNextLNode(node);
|
2018-04-12 12:13:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/////////////
|
|
|
|
|
2017-12-14 16:26:28 -08:00
|
|
|
/**
|
2018-03-16 16:42:13 -07:00
|
|
|
* Refreshes components by entering the component view and processing its bindings, queries, etc.
|
2017-12-14 16:26:28 -08:00
|
|
|
*
|
|
|
|
* @param directiveIndex
|
|
|
|
* @param elementIndex
|
|
|
|
*/
|
2018-03-13 11:48:09 -07:00
|
|
|
export function componentRefresh<T>(directiveIndex: number, elementIndex: number): void {
|
2018-03-16 16:42:13 -07:00
|
|
|
ngDevMode && assertDataInRange(elementIndex);
|
|
|
|
const element = data ![elementIndex] as LElementNode;
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(element, TNodeType.Element);
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(element.data, `Component's host node should have an LView attached.`);
|
2018-03-16 16:42:13 -07:00
|
|
|
const hostView = element.data !;
|
2018-02-23 13:17:20 -08:00
|
|
|
|
2018-03-16 16:42:13 -07:00
|
|
|
// Only attached CheckAlways components or attached, dirty OnPush components should be checked
|
|
|
|
if (viewAttached(hostView) && hostView.flags & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
|
2018-03-21 15:10:34 -07:00
|
|
|
ngDevMode && assertDataInRange(directiveIndex, directives !);
|
2018-06-04 13:07:09 -07:00
|
|
|
detectChangesInternal(hostView, element, getDirectiveInstance(directives ![directiveIndex]));
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-01-23 10:57:48 -08:00
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-03-08 16:55:47 -08:00
|
|
|
/** Returns a boolean for whether the view is attached */
|
|
|
|
function viewAttached(view: LView): boolean {
|
|
|
|
return (view.flags & LViewFlags.Attached) === LViewFlags.Attached;
|
|
|
|
}
|
|
|
|
|
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).
|
|
|
|
*
|
2018-02-28 15:00:58 +01:00
|
|
|
* This function requires CSS selectors to be provided in 2 forms: parsed (by a compiler) and text,
|
|
|
|
* un-parsed form.
|
|
|
|
*
|
|
|
|
* The parsed form is needed for efficient matching of a node against a given CSS selector.
|
|
|
|
* The un-parsed, textual form is needed for support of the ngProjectAs attribute.
|
|
|
|
*
|
|
|
|
* Having a CSS selector in 2 different formats is not ideal, but alternatives have even more
|
|
|
|
* drawbacks:
|
|
|
|
* - having only a textual form would require runtime parsing of CSS selectors;
|
|
|
|
* - we can't have only a parsed as we can't re-construct textual form from it (as entered by a
|
|
|
|
* template author).
|
|
|
|
*
|
|
|
|
* @param selectors A collection of parsed CSS selectors
|
|
|
|
* @param rawSelectors A collection of CSS selectors in the raw, un-parsed form
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-02-28 15:00:58 +01:00
|
|
|
export function projectionDef(
|
2018-03-29 16:41:45 -07:00
|
|
|
index: number, selectors?: CssSelectorList[], textSelectors?: string[]): 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] = [];
|
|
|
|
}
|
|
|
|
|
2018-05-11 20:57:37 -07:00
|
|
|
const componentNode: LElementNode = findComponentHost(currentView);
|
2018-05-24 13:13:51 -07:00
|
|
|
let componentChild: LNode|null = getChildLNode(componentNode);
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
while (componentChild !== null) {
|
2018-01-31 15:50:24 +01:00
|
|
|
// execute selector matching logic if and only if:
|
|
|
|
// - there are selectors defined
|
|
|
|
// - a node has a tag name / attributes that can be matched
|
|
|
|
if (selectors && componentChild.tNode) {
|
2018-02-28 15:00:58 +01:00
|
|
|
const matchedIdx = matchingSelectorIndex(componentChild.tNode, selectors, textSelectors !);
|
2018-01-31 15:50:24 +01:00
|
|
|
distributedNodes[matchedIdx].push(componentChild);
|
|
|
|
} else {
|
2017-12-01 14:23:03 -08:00
|
|
|
distributedNodes[0].push(componentChild);
|
|
|
|
}
|
2018-01-31 15:50:24 +01:00
|
|
|
|
2018-05-11 20:57:37 -07:00
|
|
|
componentChild = getNextLNode(componentChild);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
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) {
|
2018-02-12 22:46:15 -08:00
|
|
|
ngDevMode && assertEqual(
|
|
|
|
!!appendedFirst, !!appendedLast,
|
|
|
|
'appendedFirst can be null if and only if appendedLast is also null');
|
2018-01-25 15:32:21 +01:00
|
|
|
if (!appendedLast) {
|
|
|
|
// nothing to append
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const projectionNodeData = projectionNode.data;
|
2018-01-26 11:36:31 +01:00
|
|
|
if (projectionNodeData.tail) {
|
|
|
|
projectionNodeData.tail.pNextOrParent = appendedFirst;
|
2018-01-25 15:32:21 +01:00
|
|
|
} else {
|
2018-01-26 11:36:31 +01:00
|
|
|
projectionNodeData.head = appendedFirst;
|
2018-01-25 15:32:21 +01:00
|
|
|
}
|
2018-01-26 11:36:31 +01:00
|
|
|
projectionNodeData.tail = appendedLast;
|
2018-01-25 15:32:21 +01:00
|
|
|
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
|
2018-04-18 16:23:49 -07:00
|
|
|
* @param selectorIndex:
|
|
|
|
* - 0 when the selector is `*` (or unspecified as this is the default value),
|
|
|
|
* - 1 based index of the selector from the {@link projectionDef}
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-04-18 16:23:49 -07:00
|
|
|
export function projection(
|
2018-06-06 13:38:20 -07:00
|
|
|
nodeIndex: number, localIndex: number, selectorIndex: number = 0, attrs?: string[]): void {
|
2018-05-11 20:57:37 -07:00
|
|
|
const node = createLNode(
|
2018-05-17 12:54:57 -07:00
|
|
|
nodeIndex, TNodeType.Projection, null, null, attrs || null, {head: null, tail: null});
|
2018-04-18 16:23:49 -07:00
|
|
|
|
2018-04-16 11:49:11 -07:00
|
|
|
// `<ng-content>` has no content
|
|
|
|
isParent = false;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
// re-distribution of projectable nodes is memorized on a component's view level
|
|
|
|
const componentNode = findComponentHost(currentView);
|
2018-04-16 11:49:11 -07:00
|
|
|
const componentLView = componentNode.data !;
|
|
|
|
const nodesForSelector = componentLView.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];
|
2018-05-17 12:54:57 -07:00
|
|
|
if (nodeToProject.tNode.type === TNodeType.Projection) {
|
2018-04-16 11:49:11 -07:00
|
|
|
// Reprojecting a projection -> append the list of previously projected nodes
|
2018-01-25 15:32:21 +01:00
|
|
|
const previouslyProjected = (nodeToProject as LProjectionNode).data;
|
2018-01-26 11:36:31 +01:00
|
|
|
appendToProjectionNode(node, previouslyProjected.head, previouslyProjected.tail);
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2018-04-16 11:49:11 -07:00
|
|
|
// Projecting a single node
|
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
|
|
|
|
2018-05-29 15:08:30 -07:00
|
|
|
const currentParent = getParentLNode(node);
|
2018-01-26 11:36:31 +01:00
|
|
|
if (canInsertNativeNode(currentParent, currentView)) {
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(currentParent, TNodeType.Element);
|
2018-01-26 11:36:31 +01:00
|
|
|
// process each node in the list of projected nodes:
|
|
|
|
let nodeToProject: LNode|null = node.data.head;
|
|
|
|
const lastNodeToProject = node.data.tail;
|
|
|
|
while (nodeToProject) {
|
|
|
|
appendProjectedNode(
|
2018-04-10 17:37:11 +02:00
|
|
|
nodeToProject as LTextNode | LElementNode | LContainerNode, currentParent as LElementNode,
|
|
|
|
currentView);
|
2018-01-26 11:36:31 +01:00
|
|
|
nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent;
|
|
|
|
}
|
2018-01-25 15:32:21 +01:00
|
|
|
}
|
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;
|
2018-05-17 12:54:57 -07:00
|
|
|
while (viewRootLNode.tNode.type === TNodeType.View) {
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(lView.parent, 'lView.parent');
|
2018-01-08 20:17:13 -08:00
|
|
|
lView = lView.parent !;
|
|
|
|
viewRootLNode = lView.node;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(viewRootLNode, TNodeType.Element);
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(viewRootLNode.data, 'node.data');
|
2017-12-01 14:23:03 -08:00
|
|
|
|
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-03-15 17:33:35 +01:00
|
|
|
* @param currentView The view where LView or LContainer should be added
|
2018-05-30 13:43:14 -07:00
|
|
|
* @param hostIndex Index of the view's host node in data[]
|
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-05-30 13:43:14 -07:00
|
|
|
export function addToViewTree<T extends LView|LContainer>(
|
|
|
|
currentView: LView, hostIndex: number, state: T): T {
|
|
|
|
// TODO(kara): move next and tail properties off of LView
|
|
|
|
if (currentView.tail) {
|
|
|
|
currentView.tail.next = state;
|
|
|
|
} else if (firstTemplatePass) {
|
|
|
|
currentView.tView.childIndex = hostIndex;
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
currentView.tail = state;
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2018-02-23 13:17:20 -08:00
|
|
|
///////////////////////////////
|
|
|
|
//// Change detection
|
|
|
|
///////////////////////////////
|
|
|
|
|
|
|
|
/** If node is an OnPush component, marks its LView dirty. */
|
|
|
|
export function markDirtyIfOnPush(node: LElementNode): void {
|
|
|
|
// Because data flows down the component tree, ancestors do not need to be marked dirty
|
|
|
|
if (node.data && !(node.data.flags & LViewFlags.CheckAlways)) {
|
|
|
|
node.data.flags |= LViewFlags.Dirty;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wraps an event listener so its host view and its ancestor views will be marked dirty
|
|
|
|
* whenever the event fires. Necessary to support OnPush components.
|
|
|
|
*/
|
2018-03-01 09:46:39 -08:00
|
|
|
export function wrapListenerWithDirtyLogic(view: LView, listenerFn: (e?: any) => any): (e: Event) =>
|
|
|
|
any {
|
|
|
|
return function(e: any) {
|
|
|
|
markViewDirty(view);
|
|
|
|
return listenerFn(e);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wraps an event listener so its host view and its ancestor views will be marked dirty
|
|
|
|
* whenever the event fires. Also wraps with preventDefault behavior.
|
|
|
|
*/
|
|
|
|
export function wrapListenerWithDirtyAndDefault(
|
|
|
|
view: LView, listenerFn: (e?: any) => any): EventListener {
|
2018-04-03 11:25:13 -07:00
|
|
|
return function wrapListenerIn_markViewDirty(e: Event) {
|
2018-02-23 13:17:20 -08:00
|
|
|
markViewDirty(view);
|
2018-03-01 09:46:39 -08:00
|
|
|
if (listenerFn(e) === false) {
|
|
|
|
e.preventDefault();
|
|
|
|
// Necessary for legacy browsers that don't support preventDefault (e.g. IE)
|
|
|
|
e.returnValue = false;
|
|
|
|
}
|
2018-02-23 13:17:20 -08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Marks current view and all ancestors dirty */
|
2018-03-09 12:45:31 -08:00
|
|
|
export function markViewDirty(view: LView): void {
|
2018-02-23 13:17:20 -08:00
|
|
|
let currentView: LView|null = view;
|
|
|
|
|
|
|
|
while (currentView.parent != null) {
|
|
|
|
currentView.flags |= LViewFlags.Dirty;
|
|
|
|
currentView = currentView.parent;
|
|
|
|
}
|
|
|
|
currentView.flags |= LViewFlags.Dirty;
|
|
|
|
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(currentView !.context, 'rootContext');
|
2018-03-06 11:58:08 -08:00
|
|
|
scheduleTick(currentView !.context as RootContext);
|
2018-02-23 13:17:20 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-03-06 11:58:08 -08:00
|
|
|
/**
|
|
|
|
* Used to schedule change detection on the whole application.
|
|
|
|
*
|
|
|
|
* Unlike `tick`, `scheduleTick` coalesces multiple calls into one change detection run.
|
|
|
|
* It is usually called indirectly by calling `markDirty` when the view needs to be
|
|
|
|
* re-rendered.
|
|
|
|
*
|
|
|
|
* Typically `scheduleTick` uses `requestAnimationFrame` to coalesce multiple
|
|
|
|
* `scheduleTick` requests. The scheduling function can be overridden in
|
|
|
|
* `renderComponent`'s `scheduler` option.
|
|
|
|
*/
|
|
|
|
export function scheduleTick<T>(rootContext: RootContext) {
|
2018-02-23 13:17:20 -08:00
|
|
|
if (rootContext.clean == _CLEAN_PROMISE) {
|
|
|
|
let res: null|((val: null) => void);
|
|
|
|
rootContext.clean = new Promise<null>((r) => res = r);
|
|
|
|
rootContext.scheduler(() => {
|
2018-05-09 16:49:39 -07:00
|
|
|
tickRootContext(rootContext);
|
2018-02-23 13:17:20 -08:00
|
|
|
res !(null);
|
|
|
|
rootContext.clean = _CLEAN_PROMISE;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-06 11:58:08 -08:00
|
|
|
/**
|
|
|
|
* Used to perform change detection on the whole application.
|
|
|
|
*
|
|
|
|
* This is equivalent to `detectChanges`, but invoked on root component. Additionally, `tick`
|
|
|
|
* executes lifecycle hooks and conditionally checks components based on their
|
|
|
|
* `ChangeDetectionStrategy` and dirtiness.
|
|
|
|
*
|
|
|
|
* The preferred way to trigger change detection is to call `markDirty`. `markDirty` internally
|
|
|
|
* schedules `tick` using a scheduler in order to coalesce multiple `markDirty` calls into a
|
|
|
|
* single change detection run. By default, the scheduler is `requestAnimationFrame`, but can
|
|
|
|
* be changed when calling `renderComponent` and providing the `scheduler` option.
|
|
|
|
*/
|
|
|
|
export function tick<T>(component: T): void {
|
|
|
|
const rootView = getRootView(component);
|
2018-05-09 16:49:39 -07:00
|
|
|
const rootContext = rootView.context as RootContext;
|
|
|
|
tickRootContext(rootContext);
|
|
|
|
}
|
2018-03-06 11:58:08 -08:00
|
|
|
|
2018-05-09 16:49:39 -07:00
|
|
|
function tickRootContext(rootContext: RootContext) {
|
|
|
|
for (let i = 0; i < rootContext.components.length; i++) {
|
|
|
|
const rootComponent = rootContext.components[i];
|
|
|
|
const hostNode = _getComponentHostLElementNode(rootComponent);
|
|
|
|
|
|
|
|
ngDevMode && assertDefined(hostNode.data, 'Component host node should be attached to an LView');
|
|
|
|
renderComponentOrTemplate(hostNode, getRootView(rootComponent), rootComponent);
|
|
|
|
}
|
2018-03-06 11:58:08 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the root view from any component by walking the parent `LView` until
|
|
|
|
* reaching the root `LView`.
|
|
|
|
*
|
|
|
|
* @param component any component
|
|
|
|
*/
|
|
|
|
|
|
|
|
export function getRootView(component: any): LView {
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(component, 'component');
|
2018-03-06 11:58:08 -08:00
|
|
|
const lElementNode = _getComponentHostLElementNode(component);
|
|
|
|
let lView = lElementNode.view;
|
|
|
|
while (lView.parent) {
|
|
|
|
lView = lView.parent;
|
|
|
|
}
|
|
|
|
return lView;
|
|
|
|
}
|
|
|
|
|
2018-02-23 13:17:20 -08:00
|
|
|
/**
|
|
|
|
* Synchronously perform change detection on a component (and possibly its sub-components).
|
|
|
|
*
|
|
|
|
* This function triggers change detection in a synchronous way on a component. There should
|
|
|
|
* be very little reason to call this function directly since a preferred way to do change
|
|
|
|
* detection is to {@link markDirty} the component and wait for the scheduler to call this method
|
|
|
|
* at some future point in time. This is because a single user action often results in many
|
|
|
|
* components being invalidated and calling change detection on each component synchronously
|
|
|
|
* would be inefficient. It is better to wait until all components are marked as dirty and
|
|
|
|
* then perform single change detection across all of the components
|
|
|
|
*
|
|
|
|
* @param component The component which the change detection should be performed on.
|
|
|
|
*/
|
|
|
|
export function detectChanges<T>(component: T): void {
|
|
|
|
const hostNode = _getComponentHostLElementNode(component);
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(hostNode.data, 'Component host node should be attached to an LView');
|
2018-06-04 13:07:09 -07:00
|
|
|
detectChangesInternal(hostNode.data as LView, hostNode, component);
|
2018-03-06 11:58:08 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-03-09 20:22:18 -08:00
|
|
|
/**
|
|
|
|
* Checks the change detector and its children, and throws if any changes are detected.
|
|
|
|
*
|
|
|
|
* This is used in development mode to verify that running change detection doesn't
|
|
|
|
* introduce other changes.
|
|
|
|
*/
|
|
|
|
export function checkNoChanges<T>(component: T): void {
|
|
|
|
checkNoChangesMode = true;
|
|
|
|
try {
|
|
|
|
detectChanges(component);
|
|
|
|
} finally {
|
|
|
|
checkNoChangesMode = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-06 11:58:08 -08:00
|
|
|
/** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */
|
2018-06-04 13:07:09 -07:00
|
|
|
export function detectChangesInternal<T>(hostView: LView, hostNode: LElementNode, component: T) {
|
2018-03-16 16:42:13 -07:00
|
|
|
const oldView = enterView(hostView, hostNode);
|
2018-06-04 13:07:09 -07:00
|
|
|
const template = hostView.tView.template !;
|
2018-03-25 21:32:39 -07:00
|
|
|
|
2018-03-16 16:42:13 -07:00
|
|
|
try {
|
2018-04-10 20:57:09 -07:00
|
|
|
template(getRenderFlags(hostView), component);
|
2018-04-12 13:49:37 +02:00
|
|
|
refreshView();
|
2018-03-16 16:42:13 -07:00
|
|
|
} finally {
|
|
|
|
leaveView(oldView);
|
2018-03-06 11:58:08 -08:00
|
|
|
}
|
2018-02-23 13:17:20 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mark the component as dirty (needing change detection).
|
|
|
|
*
|
|
|
|
* Marking a component dirty will schedule a change detection on this
|
|
|
|
* component at some point in the future. Marking an already dirty
|
|
|
|
* component as dirty is a noop. Only one outstanding change detection
|
|
|
|
* can be scheduled per component tree. (Two components bootstrapped with
|
|
|
|
* separate `renderComponent` will have separate schedulers)
|
|
|
|
*
|
|
|
|
* When the root component is bootstrapped with `renderComponent`, a scheduler
|
|
|
|
* can be provided.
|
|
|
|
*
|
|
|
|
* @param component Component to mark as dirty.
|
|
|
|
*/
|
|
|
|
export function markDirty<T>(component: T) {
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(component, 'component');
|
2018-02-23 13:17:20 -08:00
|
|
|
const lElementNode = _getComponentHostLElementNode(component);
|
|
|
|
markViewDirty(lElementNode.view);
|
|
|
|
}
|
|
|
|
|
2018-02-14 11:22:14 -08:00
|
|
|
///////////////////////////////
|
|
|
|
//// Bindings & interpolations
|
|
|
|
///////////////////////////////
|
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
|
|
|
|
|
|
|
/**
|
2018-02-14 11:22:14 -08:00
|
|
|
* Initializes the binding start index. Will get inlined.
|
|
|
|
*
|
|
|
|
* This function must be called before any binding related function is called
|
|
|
|
* (ie `bind()`, `interpolationX()`, `pureFunctionX()`)
|
|
|
|
*/
|
|
|
|
function initBindings() {
|
2018-04-10 20:57:20 -07:00
|
|
|
ngDevMode && assertEqual(
|
|
|
|
currentView.bindingIndex, -1,
|
|
|
|
'Binding index should not yet be set ' + currentView.bindingIndex);
|
2018-06-01 18:14:05 -07:00
|
|
|
if (currentView.tView.bindingStartIndex === -1) {
|
|
|
|
currentView.tView.bindingStartIndex = data.length;
|
|
|
|
}
|
|
|
|
currentView.bindingIndex = currentView.tView.bindingStartIndex;
|
2018-02-14 11:22:14 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a single value binding.
|
|
|
|
*
|
|
|
|
* @param value Value to diff
|
|
|
|
*/
|
2018-05-13 21:01:37 +02:00
|
|
|
export function bind<T>(value: T): T|NO_CHANGE {
|
|
|
|
return bindingUpdated(value) ? value : NO_CHANGE;
|
2018-02-14 11:22:14 -08:00
|
|
|
}
|
|
|
|
|
2018-05-21 15:59:25 -07:00
|
|
|
/**
|
|
|
|
* Reserves slots for pure functions (`pureFunctionX` instructions)
|
|
|
|
*
|
2018-06-01 14:46:28 -07:00
|
|
|
* Bindings for pure functions are stored after the LNodes in the data array but before the binding.
|
2018-05-21 15:59:25 -07:00
|
|
|
*
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
* | LNodes ... | pure function bindings | regular bindings / interpolations |
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
* ^
|
2018-06-01 18:14:05 -07:00
|
|
|
* TView.bindingStartIndex
|
2018-05-21 15:59:25 -07:00
|
|
|
*
|
2018-06-01 18:14:05 -07:00
|
|
|
* Pure function instructions are given an offset from TView.bindingStartIndex.
|
|
|
|
* Subtracting the offset from TView.bindingStartIndex gives the first index where the bindings
|
2018-05-21 15:59:25 -07:00
|
|
|
* are stored.
|
|
|
|
*
|
|
|
|
* NOTE: reserveSlots instructions are only ever allowed at the very end of the creation block
|
|
|
|
*/
|
|
|
|
export function reserveSlots(numSlots: number) {
|
|
|
|
// Init the slots with a unique `NO_CHANGE` value so that the first change is always detected
|
2018-06-01 14:46:28 -07:00
|
|
|
// whether it happens or not during the first change detection pass - pure functions checks
|
2018-05-21 15:59:25 -07:00
|
|
|
// might be skipped when short-circuited.
|
|
|
|
data.length += numSlots;
|
|
|
|
data.fill(NO_CHANGE, -numSlots);
|
|
|
|
// We need to initialize the binding in case a `pureFunctionX` kind of binding instruction is
|
|
|
|
// called first in the update section.
|
|
|
|
initBindings();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-06-01 14:46:28 -07:00
|
|
|
* Sets up the binding index before executing any `pureFunctionX` instructions.
|
2018-05-21 15:59:25 -07:00
|
|
|
*
|
|
|
|
* The index must be restored after the pure function is executed
|
|
|
|
*
|
|
|
|
* {@link reserveSlots}
|
|
|
|
*/
|
|
|
|
export function moveBindingIndexToReservedSlot(offset: number): number {
|
|
|
|
const currentSlot = currentView.bindingIndex;
|
2018-06-01 18:14:05 -07:00
|
|
|
currentView.bindingIndex = currentView.tView.bindingStartIndex - offset;
|
2018-05-21 15:59:25 -07:00
|
|
|
return currentSlot;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restores the binding index to the given value.
|
|
|
|
*
|
|
|
|
* This function is typically used to restore the index after a `pureFunctionX` has
|
|
|
|
* been executed.
|
|
|
|
*/
|
|
|
|
export function restoreBindingIndex(index: number): void {
|
|
|
|
currentView.bindingIndex = index;
|
|
|
|
}
|
|
|
|
|
2018-02-14 11:22:14 -08:00
|
|
|
/**
|
|
|
|
* Create interpolation bindings with a variable number of expressions.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-02-16 17:20:14 -08:00
|
|
|
* If there are 1 to 8 expressions `interpolation1()` to `interpolation8()` should be used instead.
|
2018-02-16 21:20:55 -08:00
|
|
|
* Those are faster because there is no need to create an array of expressions and iterate over it.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-01-30 16:33:28 -08:00
|
|
|
* `values`:
|
|
|
|
* - has static text at even indexes,
|
2018-02-16 21:20:55 -08:00
|
|
|
* - has evaluated expressions at odd indexes.
|
2018-02-14 11:22:14 -08:00
|
|
|
*
|
|
|
|
* Returns the concatenated string when any of the arguments changes, `NO_CHANGE` otherwise.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-02-14 11:22:14 -08:00
|
|
|
export function interpolationV(values: any[]): string|NO_CHANGE {
|
2018-01-30 16:33:28 -08:00
|
|
|
ngDevMode && assertLessThan(2, values.length, 'should have at least 3 values');
|
|
|
|
ngDevMode && assertEqual(values.length % 2, 1, 'should have an odd number of values');
|
|
|
|
|
2018-02-16 21:20:55 -08:00
|
|
|
let different = false;
|
|
|
|
|
|
|
|
for (let i = 1; i < values.length; i += 2) {
|
|
|
|
// Check if bindings (odd indexes) have changed
|
|
|
|
bindingUpdated(values[i]) && (different = true);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-01-29 17:41:07 -08:00
|
|
|
|
2018-02-16 21:20:55 -08:00
|
|
|
if (!different) {
|
|
|
|
return NO_CHANGE;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-01-29 17:41:07 -08:00
|
|
|
|
2018-02-16 21:20:55 -08:00
|
|
|
// Build the updated content
|
|
|
|
let content = values[0];
|
|
|
|
for (let i = 1; i < values.length; i += 2) {
|
|
|
|
content += stringify(values[i]) + values[i + 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
return content;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-02-14 11:22:14 -08:00
|
|
|
* Creates an interpolation binding with 1 expression.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
|
|
|
* @param prefix static value used for concatenation only.
|
2018-02-16 17:20:14 -08:00
|
|
|
* @param v0 value checked for change.
|
2017-12-01 14:23:03 -08:00
|
|
|
* @param suffix static value used for concatenation only.
|
|
|
|
*/
|
2018-02-16 17:20:14 -08:00
|
|
|
export function interpolation1(prefix: string, v0: any, suffix: string): string|NO_CHANGE {
|
2018-02-16 21:20:55 -08:00
|
|
|
const different = bindingUpdated(v0);
|
2018-02-16 17:20:14 -08:00
|
|
|
|
|
|
|
return different ? prefix + stringify(v0) + suffix : NO_CHANGE;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-02-14 11:22:14 -08:00
|
|
|
/** Creates an interpolation binding with 2 expressions. */
|
|
|
|
export function interpolation2(
|
|
|
|
prefix: string, v0: any, i0: string, v1: any, suffix: string): string|NO_CHANGE {
|
2018-02-16 21:20:55 -08:00
|
|
|
const different = bindingUpdated2(v0, v1);
|
2018-02-16 17:20:14 -08:00
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
return different ? prefix + stringify(v0) + i0 + stringify(v1) + suffix : NO_CHANGE;
|
|
|
|
}
|
|
|
|
|
2018-02-14 11:22:14 -08:00
|
|
|
/** Creates an interpolation bindings with 3 expressions. */
|
|
|
|
export function interpolation3(
|
2017-12-01 14:23:03 -08:00
|
|
|
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): string|
|
|
|
|
NO_CHANGE {
|
2018-02-16 21:20:55 -08:00
|
|
|
let different = bindingUpdated2(v0, v1);
|
|
|
|
different = bindingUpdated(v2) || different;
|
2018-02-16 17:20:14 -08:00
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
return different ? prefix + stringify(v0) + i0 + stringify(v1) + i1 + stringify(v2) + suffix :
|
|
|
|
NO_CHANGE;
|
|
|
|
}
|
|
|
|
|
2018-02-14 11:22:14 -08:00
|
|
|
/** Create an interpolation binding with 4 expressions. */
|
|
|
|
export function interpolation4(
|
2017-12-01 14:23:03 -08:00
|
|
|
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any,
|
|
|
|
suffix: string): string|NO_CHANGE {
|
2018-02-16 21:20:55 -08:00
|
|
|
const different = bindingUpdated4(v0, v1, v2, v3);
|
2018-02-16 17:20:14 -08:00
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-02-14 11:22:14 -08:00
|
|
|
/** Creates an interpolation binding with 5 expressions. */
|
|
|
|
export function interpolation5(
|
2017-12-01 14:23:03 -08:00
|
|
|
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 {
|
2018-02-16 21:20:55 -08:00
|
|
|
let different = bindingUpdated4(v0, v1, v2, v3);
|
|
|
|
different = bindingUpdated(v4) || different;
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-02-14 11:22:14 -08:00
|
|
|
/** Creates an interpolation binding with 6 expressions. */
|
|
|
|
export function interpolation6(
|
2017-12-01 14:23:03 -08:00
|
|
|
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 {
|
2018-02-16 21:20:55 -08:00
|
|
|
let different = bindingUpdated4(v0, v1, v2, v3);
|
|
|
|
different = bindingUpdated2(v4, v5) || different;
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-02-14 11:22:14 -08:00
|
|
|
/** Creates an interpolation binding with 7 expressions. */
|
|
|
|
export function interpolation7(
|
2017-12-01 14:23:03 -08:00
|
|
|
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 {
|
2018-02-16 21:20:55 -08:00
|
|
|
let different = bindingUpdated4(v0, v1, v2, v3);
|
|
|
|
different = bindingUpdated2(v4, v5) || different;
|
|
|
|
different = bindingUpdated(v6) || different;
|
2018-02-16 17:20:14 -08:00
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-02-14 11:22:14 -08:00
|
|
|
/** Creates an interpolation binding with 8 expressions. */
|
|
|
|
export function interpolation8(
|
2017-12-01 14:23:03 -08:00
|
|
|
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 {
|
2018-02-16 21:20:55 -08:00
|
|
|
let different = bindingUpdated4(v0, v1, v2, v3);
|
|
|
|
different = bindingUpdated4(v4, v5, v6, v7) || different;
|
2018-02-16 17:20:14 -08:00
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-02-16 16:58:07 -08:00
|
|
|
/** Store a value in the `data` at a given `index`. */
|
|
|
|
export function store<T>(index: number, value: T): void {
|
|
|
|
// 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
|
|
|
|
if (index >= tData.length) {
|
|
|
|
tData[index] = null;
|
|
|
|
}
|
|
|
|
data[index] = value;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-02-16 16:58:07 -08:00
|
|
|
/** Retrieves a value from the `data`. */
|
|
|
|
export function load<T>(index: number): T {
|
2018-03-21 15:10:34 -07:00
|
|
|
ngDevMode && assertDataInRange(index);
|
2018-02-16 16:58:07 -08:00
|
|
|
return data[index];
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-03-21 15:10:34 -07:00
|
|
|
/** Retrieves a value from the `directives` array. */
|
|
|
|
export function loadDirective<T>(index: number): T {
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(directives, 'Directives array should be defined if reading a dir.');
|
2018-03-21 15:10:34 -07:00
|
|
|
ngDevMode && assertDataInRange(index, directives !);
|
|
|
|
return directives ![index];
|
|
|
|
}
|
|
|
|
|
2018-02-14 13:37:54 -08:00
|
|
|
/** Gets the current binding value and increments the binding index. */
|
|
|
|
export function consumeBinding(): any {
|
2018-04-10 20:57:20 -07:00
|
|
|
ngDevMode && assertDataInRange(currentView.bindingIndex);
|
2018-02-14 13:37:54 -08:00
|
|
|
ngDevMode &&
|
2018-04-10 20:57:20 -07:00
|
|
|
assertNotEqual(
|
|
|
|
data[currentView.bindingIndex], NO_CHANGE, 'Stored value should never be NO_CHANGE.');
|
|
|
|
return data[currentView.bindingIndex++];
|
2018-02-14 13:37:54 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Updates binding if changed, then returns whether it was updated. */
|
|
|
|
export function bindingUpdated(value: any): boolean {
|
|
|
|
ngDevMode && assertNotEqual(value, NO_CHANGE, 'Incoming value should never be NO_CHANGE.');
|
2018-06-01 18:14:05 -07:00
|
|
|
if (currentView.bindingIndex === -1) initBindings();
|
2018-02-14 13:37:54 -08:00
|
|
|
|
2018-06-01 18:14:05 -07:00
|
|
|
if (currentView.bindingIndex >= data.length) {
|
|
|
|
data[currentView.bindingIndex++] = value;
|
2018-04-10 20:57:20 -07:00
|
|
|
} else if (isDifferent(data[currentView.bindingIndex], value)) {
|
|
|
|
throwErrorIfNoChangesMode(
|
|
|
|
creationMode, checkNoChangesMode, data[currentView.bindingIndex], value);
|
2018-06-01 18:14:05 -07:00
|
|
|
data[currentView.bindingIndex++] = value;
|
2018-02-14 13:37:54 -08:00
|
|
|
} else {
|
2018-04-10 20:57:20 -07:00
|
|
|
currentView.bindingIndex++;
|
2018-02-14 13:37:54 -08:00
|
|
|
return false;
|
|
|
|
}
|
2018-03-09 20:22:18 -08:00
|
|
|
return true;
|
2018-02-14 13:37:54 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Updates binding if changed, then returns the latest value. */
|
|
|
|
export function checkAndUpdateBinding(value: any): any {
|
|
|
|
bindingUpdated(value);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Updates 2 bindings if changed, then returns whether either was updated. */
|
|
|
|
export function bindingUpdated2(exp1: any, exp2: any): boolean {
|
|
|
|
const different = bindingUpdated(exp1);
|
|
|
|
return bindingUpdated(exp2) || different;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Updates 4 bindings if changed, then returns whether any was updated. */
|
|
|
|
export function bindingUpdated4(exp1: any, exp2: any, exp3: any, exp4: any): boolean {
|
|
|
|
const different = bindingUpdated2(exp1, exp2);
|
|
|
|
return bindingUpdated2(exp3, exp4) || different;
|
|
|
|
}
|
|
|
|
|
2018-02-16 16:23:27 +01:00
|
|
|
export function getTView(): TView {
|
|
|
|
return currentView.tView;
|
|
|
|
}
|
|
|
|
|
2018-01-30 15:37:01 -08:00
|
|
|
export function getDirectiveInstance<T>(instanceOrArray: T | [T]): T {
|
2018-03-21 15:10:34 -07:00
|
|
|
// Directives with content queries store an array in directives[directiveIndex]
|
2018-01-30 15:37:01 -08:00
|
|
|
// with the instance as the first index
|
|
|
|
return Array.isArray(instanceOrArray) ? instanceOrArray[0] : instanceOrArray;
|
|
|
|
}
|
|
|
|
|
2018-01-17 09:45:40 -08:00
|
|
|
export function assertPreviousIsParent() {
|
2018-02-12 22:46:15 -08:00
|
|
|
assertEqual(isParent, true, 'previousOrParentNode should be a parent');
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
function assertHasParent() {
|
2018-05-09 16:49:39 -07:00
|
|
|
assertDefined(getParentLNode(previousOrParentNode), 'previousOrParentNode should have a parent');
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2017-12-08 11:48:54 -08:00
|
|
|
function assertDataInRange(index: number, arr?: any[]) {
|
|
|
|
if (arr == null) arr = data;
|
2018-02-12 22:46:15 -08:00
|
|
|
assertLessThan(index, arr ? arr.length : 0, 'index expected to be a valid data index');
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-01-18 13:27:01 -08:00
|
|
|
|
2018-03-21 15:10:34 -07:00
|
|
|
function assertDataNext(index: number, arr?: any[]) {
|
|
|
|
if (arr == null) arr = data;
|
2018-03-27 11:01:52 -07:00
|
|
|
assertEqual(
|
|
|
|
arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`);
|
2018-01-17 17:55:55 +01:00
|
|
|
}
|
2018-02-23 13:17:20 -08:00
|
|
|
|
2018-05-21 15:59:25 -07:00
|
|
|
/**
|
2018-06-01 14:46:28 -07:00
|
|
|
* On the first template pass, the reserved slots should be set `NO_CHANGE`.
|
2018-05-21 15:59:25 -07:00
|
|
|
*
|
2018-06-01 14:46:28 -07:00
|
|
|
* If not, they might not have been actually reserved.
|
2018-05-21 15:59:25 -07:00
|
|
|
*/
|
|
|
|
export function assertReservedSlotInitialized(slotOffset: number, numSlots: number) {
|
|
|
|
if (firstTemplatePass) {
|
2018-06-01 18:14:05 -07:00
|
|
|
const startIndex = currentView.tView.bindingStartIndex - slotOffset;
|
2018-05-21 15:59:25 -07:00
|
|
|
for (let i = 0; i < numSlots; i++) {
|
|
|
|
assertEqual(
|
|
|
|
data[startIndex + i], NO_CHANGE,
|
|
|
|
'The reserved slots should be set to `NO_CHANGE` on first template pass');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-23 13:17:20 -08:00
|
|
|
export function _getComponentHostLElementNode<T>(component: T): LElementNode {
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(component, 'expecting component got null');
|
2018-02-23 13:17:20 -08:00
|
|
|
const lElementNode = (component as any)[NG_HOST_SYMBOL] as LElementNode;
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(component, 'object is not a component');
|
2018-02-23 13:17:20 -08:00
|
|
|
return lElementNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const CLEAN_PROMISE = _CLEAN_PROMISE;
|
2018-03-25 21:32:39 -07:00
|
|
|
export const ROOT_DIRECTIVE_INDICES = _ROOT_DIRECTIVE_INDICES;
|