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
|
|
|
|
*/
|
|
|
|
|
2019-01-10 23:45:02 -08:00
|
|
|
import {InjectFlags, InjectionToken, Injector} from '../di';
|
2018-11-12 18:52:51 -08:00
|
|
|
import {resolveForwardRef} from '../di/forward_ref';
|
2019-01-09 13:49:16 -08:00
|
|
|
import {Type} from '../interface/type';
|
2019-01-10 13:34:39 -08:00
|
|
|
import {validateAttribute, validateProperty} from '../sanitization/sanitization';
|
2018-05-09 16:49:39 -07:00
|
|
|
import {Sanitizer} from '../sanitization/security';
|
2018-07-11 10:58:18 -07:00
|
|
|
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
2019-01-09 13:49:16 -08:00
|
|
|
import {assertDataInRange, assertDefined, assertEqual, assertLessThan, assertNotEqual} from '../util/assert';
|
2019-01-11 21:14:42 +01:00
|
|
|
import {isObservable} from '../util/lang';
|
2018-11-21 13:43:00 +01:00
|
|
|
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../util/ng_reflect';
|
2018-12-13 15:51:47 -08:00
|
|
|
|
2019-01-09 13:49:16 -08:00
|
|
|
import {assertHasParent, assertPreviousIsParent} from './assert';
|
2018-11-21 21:14:06 -08:00
|
|
|
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from './bindings';
|
2018-10-12 15:02:54 -07:00
|
|
|
import {attachPatchData, getComponentViewByInstance} from './context_discovery';
|
2018-10-18 09:23:18 +02:00
|
|
|
import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreateNodeInjectorForNode, injectAttributeImpl} from './di';
|
2018-11-21 21:14:06 -08:00
|
|
|
import {throwMultipleComponentError} from './errors';
|
2018-12-20 17:23:25 -08:00
|
|
|
import {executeHooks, executeInitHooks, registerPostOrderHooks, registerPreOrderHooks} from './hooks';
|
2018-10-12 15:02:54 -07:00
|
|
|
import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container';
|
2018-12-13 15:51:47 -08:00
|
|
|
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
2018-11-28 15:54:38 -08:00
|
|
|
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from './interfaces/injector';
|
2018-11-13 09:36:30 +01:00
|
|
|
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from './interfaces/node';
|
2018-10-03 14:09:59 -07:00
|
|
|
import {PlayerFactory} from './interfaces/player';
|
2018-07-03 20:04:36 -07:00
|
|
|
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
|
2018-01-29 14:51:37 +01:00
|
|
|
import {LQueries} from './interfaces/query';
|
2018-12-19 15:03:47 -08:00
|
|
|
import {GlobalTargetResolver, ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
|
2018-11-13 09:36:30 +01:00
|
|
|
import {SanitizerFn} from './interfaces/sanitization';
|
2019-01-18 17:38:39 +01:00
|
|
|
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view';
|
2018-06-22 15:37:38 +02:00
|
|
|
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
2019-01-09 14:54:47 +01:00
|
|
|
import {appendChild, appendProjectedNode, createTextNode, getLViewChild, insertView, removeView} from './node_manipulation';
|
2018-03-29 16:41:45 -07:00
|
|
|
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
2019-01-30 16:38:59 +01:00
|
|
|
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getCurrentQueryIndex, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode} from './state';
|
2018-12-13 15:51:47 -08:00
|
|
|
import {getInitialClassNameValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialStylesAndClasses, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
|
2018-10-03 14:09:59 -07:00
|
|
|
import {BoundPlayerFactory} from './styling/player_factory';
|
2018-12-13 15:51:47 -08:00
|
|
|
import {createEmptyStylingContext, getStylingContext, hasClassInput, hasStyling, isAnimationProp} from './styling/util';
|
2018-10-18 14:47:53 -07:00
|
|
|
import {NO_CHANGE} from './tokens';
|
2019-01-24 08:53:00 -08:00
|
|
|
import {INTERPOLATION_DELIMITER, findComponentView, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isContentQueryHost, loadInternal, readElementValue, readPatchedLView, renderStringify} from './util';
|
2018-11-21 21:14:06 -08:00
|
|
|
|
|
|
|
|
2018-12-13 15:51:47 -08:00
|
|
|
|
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-02-07 22:19:24 -08:00
|
|
|
const enum BindingDirection {
|
|
|
|
Input,
|
|
|
|
Output,
|
|
|
|
}
|
|
|
|
|
2018-04-12 13:49:37 +02:00
|
|
|
/**
|
|
|
|
* Refreshes the view, executing the following steps in that order:
|
2018-06-21 11:49:03 +02:00
|
|
|
* triggers init hooks, refreshes dynamic embedded views, triggers content hooks, sets host
|
2018-07-17 11:45:49 -07:00
|
|
|
* bindings, refreshes child components.
|
2018-04-12 13:49:37 +02:00
|
|
|
* Note: view hooks are triggered later when leaving the view.
|
2018-06-21 11:49:03 +02:00
|
|
|
*/
|
2018-12-18 16:58:51 -08:00
|
|
|
export function refreshDescendantViews(lView: LView) {
|
2018-11-21 21:14:06 -08:00
|
|
|
const tView = lView[TVIEW];
|
2018-07-17 11:45:49 -07:00
|
|
|
// This needs to be set before children are processed to support recursive components
|
2018-10-18 09:23:18 +02:00
|
|
|
tView.firstTemplatePass = false;
|
2018-07-17 11:45:49 -07:00
|
|
|
|
2019-01-18 14:16:41 +01:00
|
|
|
// Resetting the bindingIndex of the current LView as the next steps may trigger change detection.
|
|
|
|
lView[BINDING_INDEX] = tView.bindingStartIndex;
|
|
|
|
|
2018-12-18 16:58:51 -08:00
|
|
|
// If this is a creation pass, we should not call lifecycle hooks or evaluate bindings.
|
|
|
|
// This will be done in the update pass.
|
|
|
|
if (!isCreationMode(lView)) {
|
2018-10-30 22:10:23 -07:00
|
|
|
const checkNoChangesMode = getCheckNoChangesMode();
|
|
|
|
|
2018-12-18 16:58:51 -08:00
|
|
|
executeInitHooks(lView, tView, checkNoChangesMode);
|
2018-10-27 16:01:26 -07:00
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
refreshDynamicEmbeddedViews(lView);
|
2018-08-02 14:27:36 -07:00
|
|
|
|
2018-10-30 22:10:23 -07:00
|
|
|
// Content query results must be refreshed before content hooks are called.
|
|
|
|
refreshContentQueries(tView);
|
2018-08-02 14:27:36 -07:00
|
|
|
|
2019-01-18 17:38:39 +01:00
|
|
|
executeHooks(
|
|
|
|
lView, tView.contentHooks, tView.contentCheckHooks, checkNoChangesMode,
|
|
|
|
InitPhaseState.AfterContentInitHooksToBeRun);
|
2018-11-12 11:44:47 -08:00
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
setHostBindings(tView, lView);
|
2018-04-12 13:49:37 +02:00
|
|
|
}
|
|
|
|
|
2018-12-18 16:58:51 -08:00
|
|
|
refreshChildComponents(tView.components);
|
2018-03-16 16:42:13 -07:00
|
|
|
}
|
2018-03-13 11:48:09 -07:00
|
|
|
|
2018-07-10 10:43:07 +02:00
|
|
|
|
2018-03-16 16:42:13 -07:00
|
|
|
/** Sets the host bindings for the current view. */
|
2018-11-21 21:14:06 -08:00
|
|
|
export function setHostBindings(tView: TView, viewData: LView): void {
|
2018-10-08 16:04:46 -07:00
|
|
|
if (tView.expandoInstructions) {
|
2018-10-18 09:23:18 +02:00
|
|
|
let bindingRootIndex = viewData[BINDING_INDEX] = tView.expandoStartIndex;
|
|
|
|
setBindingRoot(bindingRootIndex);
|
2018-10-08 16:04:46 -07:00
|
|
|
let currentDirectiveIndex = -1;
|
|
|
|
let currentElementIndex = -1;
|
|
|
|
for (let i = 0; i < tView.expandoInstructions.length; i++) {
|
|
|
|
const instruction = tView.expandoInstructions[i];
|
|
|
|
if (typeof instruction === 'number') {
|
|
|
|
if (instruction <= 0) {
|
|
|
|
// Negative numbers mean that we are starting new EXPANDO block and need to update
|
|
|
|
// the current element and directive index.
|
|
|
|
currentElementIndex = -instruction;
|
2018-10-25 19:10:32 -07:00
|
|
|
// Injector block and providers are taken into account.
|
|
|
|
const providerCount = (tView.expandoInstructions[++i] as number);
|
2018-11-28 15:54:38 -08:00
|
|
|
bindingRootIndex += INJECTOR_BLOOM_PARENT_SIZE + providerCount;
|
2018-10-18 09:23:18 +02:00
|
|
|
|
2018-10-08 16:04:46 -07:00
|
|
|
currentDirectiveIndex = bindingRootIndex;
|
|
|
|
} else {
|
|
|
|
// This is either the injector size (so the binding root can skip over directives
|
|
|
|
// and get to the first set of host bindings on this node) or the host var count
|
|
|
|
// (to get to the next set of host bindings on this node).
|
|
|
|
bindingRootIndex += instruction;
|
2018-09-28 21:26:45 -07:00
|
|
|
}
|
2018-10-18 09:23:18 +02:00
|
|
|
setBindingRoot(bindingRootIndex);
|
2018-10-08 16:04:46 -07:00
|
|
|
} else {
|
|
|
|
// If it's not a number, it's a host binding function that needs to be executed.
|
2018-11-27 12:05:26 -08:00
|
|
|
if (instruction !== null) {
|
|
|
|
viewData[BINDING_INDEX] = bindingRootIndex;
|
|
|
|
instruction(
|
2019-01-14 17:39:21 -08:00
|
|
|
RenderFlags.Update, readElementValue(viewData[currentDirectiveIndex]),
|
2018-11-27 12:05:26 -08:00
|
|
|
currentElementIndex);
|
|
|
|
}
|
2018-10-08 16:04:46 -07:00
|
|
|
currentDirectiveIndex++;
|
2018-09-28 21:26:45 -07:00
|
|
|
}
|
2018-03-16 16:42:13 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-10 10:43:07 +02:00
|
|
|
/** Refreshes content queries for all directives in the given view. */
|
|
|
|
function refreshContentQueries(tView: TView): void {
|
|
|
|
if (tView.contentQueries != null) {
|
2019-01-23 11:54:43 -08:00
|
|
|
setCurrentQueryIndex(0);
|
|
|
|
for (let i = 0; i < tView.contentQueries.length; i++) {
|
2018-07-10 10:43:07 +02:00
|
|
|
const directiveDefIdx = tView.contentQueries[i];
|
2018-10-08 16:04:46 -07:00
|
|
|
const directiveDef = tView.data[directiveDefIdx] as DirectiveDef<any>;
|
2019-01-23 11:54:43 -08:00
|
|
|
directiveDef.contentQueriesRefresh !(directiveDefIdx - HEADER_OFFSET);
|
2018-07-10 10:43:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-16 16:42:13 -07:00
|
|
|
/** Refreshes child components in the current view. */
|
2018-12-18 16:58:51 -08:00
|
|
|
function refreshChildComponents(components: number[] | null): void {
|
2018-03-13 11:48:09 -07:00
|
|
|
if (components != null) {
|
2018-07-17 11:45:49 -07:00
|
|
|
for (let i = 0; i < components.length; i++) {
|
2018-12-18 16:58:51 -08:00
|
|
|
componentRefresh(components[i]);
|
2018-03-13 11:48:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
export function createLView<T>(
|
|
|
|
parentLView: LView | null, tView: TView, context: T | null, flags: LViewFlags,
|
2018-11-21 21:14:06 -08:00
|
|
|
rendererFactory?: RendererFactory3 | null, renderer?: Renderer3 | null,
|
2018-11-21 21:14:06 -08:00
|
|
|
sanitizer?: Sanitizer | null, injector?: Injector | null): LView {
|
|
|
|
const lView = tView.blueprint.slice() as LView;
|
2019-01-18 17:38:39 +01:00
|
|
|
lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass;
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[PARENT] = lView[DECLARATION_VIEW] = parentLView;
|
|
|
|
lView[CONTEXT] = context;
|
|
|
|
lView[RENDERER_FACTORY] = (rendererFactory || parentLView && parentLView[RENDERER_FACTORY]) !;
|
|
|
|
ngDevMode && assertDefined(lView[RENDERER_FACTORY], 'RendererFactory is required');
|
|
|
|
lView[RENDERER] = (renderer || parentLView && parentLView[RENDERER]) !;
|
|
|
|
ngDevMode && assertDefined(lView[RENDERER], 'Renderer is required');
|
|
|
|
lView[SANITIZER] = sanitizer || parentLView && parentLView[SANITIZER] || null !;
|
|
|
|
lView[INJECTOR as any] = injector || parentLView && parentLView[INJECTOR] || null;
|
|
|
|
return lView;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-10-12 18:49:00 -07:00
|
|
|
* Create and stores the TNode, and hooks it up to the tree.
|
2018-05-11 20:57:37 -07:00
|
|
|
*
|
2018-10-12 18:49:00 -07:00
|
|
|
* @param index The index at which the TNode should be saved (null if view, since they are not
|
2018-06-07 22:42:32 -07:00
|
|
|
* saved).
|
2018-10-12 18:49:00 -07:00
|
|
|
* @param type The type of TNode to create
|
|
|
|
* @param native The native element for this node, if applicable
|
2018-05-11 20:57:37 -07:00
|
|
|
* @param name The tag name of the associated native element, if applicable
|
|
|
|
* @param attrs Any attrs for the native element, if applicable
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-09-13 16:07:23 -07:00
|
|
|
export function createNodeAtIndex(
|
2018-06-01 14:46:28 -07:00
|
|
|
index: number, type: TNodeType.Element, native: RElement | RText | null, name: string | null,
|
2018-10-12 18:49:00 -07:00
|
|
|
attrs: TAttributes | null): TElementNode;
|
2018-09-13 16:07:23 -07:00
|
|
|
export function createNodeAtIndex(
|
2018-06-06 17:30:48 +02:00
|
|
|
index: number, type: TNodeType.Container, native: RComment, name: string | null,
|
2018-10-12 18:49:00 -07:00
|
|
|
attrs: TAttributes | null): TContainerNode;
|
2018-09-13 16:07:23 -07:00
|
|
|
export function createNodeAtIndex(
|
2018-10-12 18:49:00 -07:00
|
|
|
index: number, type: TNodeType.Projection, native: null, name: null,
|
|
|
|
attrs: TAttributes | null): TProjectionNode;
|
2018-09-13 16:07:23 -07:00
|
|
|
export function createNodeAtIndex(
|
2018-12-12 15:23:12 -08:00
|
|
|
index: number, type: TNodeType.ElementContainer, native: RComment, name: string | null,
|
2018-10-12 18:49:00 -07:00
|
|
|
attrs: TAttributes | null): TElementContainerNode;
|
2018-11-13 09:36:30 +01:00
|
|
|
export function createNodeAtIndex(
|
|
|
|
index: number, type: TNodeType.IcuContainer, native: RComment, name: null,
|
|
|
|
attrs: TAttributes | null): TElementContainerNode;
|
2018-09-13 16:07:23 -07:00
|
|
|
export function createNodeAtIndex(
|
2018-06-06 17:30:48 +02:00
|
|
|
index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null,
|
2018-11-13 09:36:30 +01:00
|
|
|
attrs: TAttributes | null): TElementNode&TContainerNode&TElementContainerNode&TProjectionNode&
|
|
|
|
TIcuContainerNode {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const tView = lView[TVIEW];
|
2018-10-12 18:49:00 -07:00
|
|
|
const adjustedIndex = index + HEADER_OFFSET;
|
|
|
|
ngDevMode &&
|
2018-11-21 21:14:06 -08:00
|
|
|
assertLessThan(adjustedIndex, lView.length, `Slot should have been initialized with null`);
|
|
|
|
lView[adjustedIndex] = native;
|
2018-08-29 13:52:03 -07:00
|
|
|
|
2019-01-24 23:50:12 +00:00
|
|
|
const previousOrParentTNode = getPreviousOrParentTNode();
|
|
|
|
const isParent = getIsParent();
|
2018-10-12 18:49:00 -07:00
|
|
|
let tNode = tView.data[adjustedIndex] as TNode;
|
|
|
|
if (tNode == null) {
|
2019-01-24 23:50:12 +00:00
|
|
|
const parent =
|
|
|
|
isParent ? previousOrParentTNode : previousOrParentTNode && previousOrParentTNode.parent;
|
|
|
|
|
|
|
|
// 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 parentInSameView = parent && parent !== lView[HOST_NODE];
|
|
|
|
const tParentNode = parentInSameView ? parent as TElementNode | TContainerNode : null;
|
|
|
|
|
|
|
|
tNode = tView.data[adjustedIndex] = createTNode(tParentNode, type, adjustedIndex, name, attrs);
|
2018-12-17 14:19:25 +01:00
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-12-17 14:19:25 +01:00
|
|
|
// Now link ourselves into the tree.
|
|
|
|
// We need this even if tNode exists, otherwise we might end up pointing to unexisting tNodes when
|
|
|
|
// we use i18n (especially with ICU expressions that update the DOM during the update phase).
|
|
|
|
if (previousOrParentTNode) {
|
|
|
|
if (isParent && previousOrParentTNode.child == null &&
|
|
|
|
(tNode.parent !== null || previousOrParentTNode.type === TNodeType.View)) {
|
|
|
|
// We are in the same view, which means we are adding content node to the parent view.
|
|
|
|
previousOrParentTNode.child = tNode;
|
|
|
|
} else if (!isParent) {
|
|
|
|
previousOrParentTNode.next = tNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
2018-05-16 05:56:01 -07:00
|
|
|
|
2018-12-17 14:19:25 +01:00
|
|
|
if (tView.firstChild == null) {
|
2018-10-12 18:49:00 -07:00
|
|
|
tView.firstChild = tNode;
|
2018-05-16 05:56:01 -07:00
|
|
|
}
|
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
setPreviousOrParentTNode(tNode);
|
|
|
|
setIsParent(true);
|
2018-09-13 16:07:23 -07:00
|
|
|
return tNode as TElementNode & TViewNode & TContainerNode & TElementContainerNode &
|
2018-11-13 09:36:30 +01:00
|
|
|
TProjectionNode & TIcuContainerNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2019-01-24 23:50:12 +00:00
|
|
|
export function assignTViewNodeToLView(
|
|
|
|
tView: TView, tParentNode: TNode | null, index: number, lView: LView): TViewNode {
|
2018-10-12 18:49:00 -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.
|
2019-01-24 23:50:12 +00:00
|
|
|
let tNode = tView.node;
|
|
|
|
if (tNode == null) {
|
|
|
|
ngDevMode && tParentNode &&
|
|
|
|
assertNodeOfPossibleTypes(tParentNode, TNodeType.Element, TNodeType.Container);
|
|
|
|
tView.node = tNode = createTNode(
|
|
|
|
tParentNode as TElementNode | TContainerNode | null, //
|
|
|
|
TNodeType.View, index, null, null) as TViewNode;
|
2018-10-12 18:49:00 -07:00
|
|
|
}
|
|
|
|
|
2019-01-24 23:50:12 +00:00
|
|
|
return lView[HOST_NODE] = tNode as TViewNode;
|
2018-10-12 18:49:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-08-21 00:03:21 -07:00
|
|
|
/**
|
2018-10-12 18:49:00 -07:00
|
|
|
* When elements are created dynamically after a view blueprint is created (e.g. through
|
2018-08-21 00:03:21 -07:00
|
|
|
* i18nApply() or ComponentFactory.create), we need to adjust the blueprint for future
|
|
|
|
* template passes.
|
|
|
|
*/
|
2018-11-21 21:14:06 -08:00
|
|
|
export function allocExpando(view: LView) {
|
2018-08-21 00:03:21 -07:00
|
|
|
const tView = view[TVIEW];
|
|
|
|
if (tView.firstTemplatePass) {
|
2018-10-08 16:04:46 -07:00
|
|
|
tView.expandoStartIndex++;
|
2018-08-21 00:03:21 -07:00
|
|
|
tView.blueprint.push(null);
|
2018-11-13 09:36:30 +01:00
|
|
|
tView.data.push(null);
|
2018-08-21 00:03:21 -07:00
|
|
|
view.push(null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
|
|
|
|
//////////////////////////
|
|
|
|
//// Render
|
|
|
|
//////////////////////////
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
2018-03-25 21:32:39 -07:00
|
|
|
* @param hostNode Existing node to render into.
|
2018-08-15 18:37:03 -07:00
|
|
|
* @param templateFn Template function with the instructions.
|
2018-08-16 18:53:21 -07:00
|
|
|
* @param consts The number of nodes, local refs, and pipes in this template
|
2017-12-01 14:23:03 -08:00
|
|
|
* @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>(
|
2018-08-18 11:14:50 -07:00
|
|
|
hostNode: RElement, templateFn: ComponentTemplate<T>, consts: number, vars: number, context: T,
|
2018-11-21 21:14:06 -08:00
|
|
|
providedRendererFactory: RendererFactory3, hostView: LView | null,
|
2018-05-09 15:30:16 -07:00
|
|
|
directives?: DirectiveDefListOrFactory | null, pipes?: PipeDefListOrFactory | null,
|
2018-11-21 21:14:06 -08:00
|
|
|
sanitizer?: Sanitizer | null): LView {
|
2018-10-12 15:02:54 -07:00
|
|
|
if (hostView == null) {
|
2018-08-22 16:57:40 -07:00
|
|
|
resetComponentState();
|
2018-10-18 09:23:18 +02:00
|
|
|
const renderer = providedRendererFactory.createRenderer(null, null);
|
2018-09-13 16:07:23 -07:00
|
|
|
|
|
|
|
// We need to create a root view so it's possible to look up the host element through its index
|
2018-11-21 21:14:06 -08:00
|
|
|
const hostLView = createLView(
|
2018-11-21 21:14:06 -08:00
|
|
|
null, createTView(-1, null, 1, 0, null, null, null), {},
|
|
|
|
LViewFlags.CheckAlways | LViewFlags.IsRoot, providedRendererFactory, renderer);
|
|
|
|
enterView(hostLView, null); // SUSPECT! why do we need to enter the View?
|
2018-09-13 16:07:23 -07:00
|
|
|
|
|
|
|
const componentTView =
|
2018-08-18 11:14:50 -07:00
|
|
|
getOrCreateTView(templateFn, consts, vars, directives || null, pipes || null, null);
|
2018-11-21 21:14:06 -08:00
|
|
|
hostView = createLView(
|
2018-11-21 21:14:06 -08:00
|
|
|
hostLView, componentTView, context, LViewFlags.CheckAlways, providedRendererFactory,
|
|
|
|
renderer, sanitizer);
|
2018-10-12 18:49:00 -07:00
|
|
|
hostView[HOST_NODE] = createNodeAtIndex(0, TNodeType.Element, hostNode, null, null);
|
2017-12-11 16:30:46 +01:00
|
|
|
}
|
2018-12-18 16:58:51 -08:00
|
|
|
renderComponentOrTemplate(hostView, context, templateFn);
|
2018-10-12 15:02:54 -07:00
|
|
|
return hostView;
|
2017-12-11 16:30:46 +01:00
|
|
|
}
|
|
|
|
|
2018-06-22 15:37:38 +02:00
|
|
|
/**
|
|
|
|
* Used for creating the LViewNode of a dynamic embedded view,
|
|
|
|
* either through ViewContainerRef.createEmbeddedView() or TemplateRef.createEmbeddedView().
|
|
|
|
* Such lViewNode will then be renderer with renderEmbeddedTemplate() (see below).
|
|
|
|
*/
|
2018-09-13 16:07:23 -07:00
|
|
|
export function createEmbeddedViewAndNode<T>(
|
2018-11-21 21:14:06 -08:00
|
|
|
tView: TView, context: T, declarationView: LView, renderer: Renderer3, queries: LQueries | null,
|
|
|
|
injectorIndex: number): LView {
|
2018-10-18 09:23:18 +02:00
|
|
|
const _isParent = getIsParent();
|
|
|
|
const _previousOrParentTNode = getPreviousOrParentTNode();
|
|
|
|
setIsParent(true);
|
|
|
|
setPreviousOrParentTNode(null !);
|
2018-06-22 15:37:38 +02:00
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = createLView(declarationView, tView, context, LViewFlags.CheckAlways);
|
2018-07-18 01:59:49 +00:00
|
|
|
lView[DECLARATION_VIEW] = declarationView;
|
2018-07-17 11:45:49 -07:00
|
|
|
|
2018-06-22 15:37:38 +02:00
|
|
|
if (queries) {
|
|
|
|
lView[QUERIES] = queries.createView();
|
|
|
|
}
|
2019-01-24 23:50:12 +00:00
|
|
|
assignTViewNodeToLView(tView, null, -1, lView);
|
2018-06-22 15:37:38 +02:00
|
|
|
|
2018-09-28 21:26:45 -07:00
|
|
|
if (tView.firstTemplatePass) {
|
|
|
|
tView.node !.injectorIndex = injectorIndex;
|
|
|
|
}
|
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
setIsParent(_isParent);
|
|
|
|
setPreviousOrParentTNode(_previousOrParentTNode);
|
2018-09-13 16:07:23 -07:00
|
|
|
return lView;
|
2018-06-22 15:37:38 +02:00
|
|
|
}
|
|
|
|
|
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-12-18 16:58:51 -08:00
|
|
|
export function renderEmbeddedTemplate<T>(viewToRender: LView, tView: TView, context: T) {
|
2018-10-18 09:23:18 +02:00
|
|
|
const _isParent = getIsParent();
|
|
|
|
const _previousOrParentTNode = getPreviousOrParentTNode();
|
2018-11-21 21:14:06 -08:00
|
|
|
let oldView: LView;
|
2018-09-13 16:07:23 -07:00
|
|
|
if (viewToRender[FLAGS] & LViewFlags.IsRoot) {
|
2018-07-20 14:32:23 +02:00
|
|
|
// This is a root view inside the view tree
|
2018-10-26 12:27:40 +02:00
|
|
|
tickRootContext(getRootContext(viewToRender));
|
2018-07-20 14:32:23 +02:00
|
|
|
} else {
|
|
|
|
try {
|
2018-10-18 09:23:18 +02:00
|
|
|
setIsParent(true);
|
|
|
|
setPreviousOrParentTNode(null !);
|
2018-04-12 13:49:37 +02:00
|
|
|
|
2018-09-13 16:07:23 -07:00
|
|
|
oldView = enterView(viewToRender, viewToRender[HOST_NODE]);
|
2018-07-20 14:32:23 +02:00
|
|
|
namespaceHTML();
|
2018-12-18 16:58:51 -08:00
|
|
|
tView.template !(getRenderFlags(viewToRender), context);
|
|
|
|
// This must be set to false immediately after the first creation run because in an
|
|
|
|
// ngFor loop, all the views will be created together before update mode runs and turns
|
|
|
|
// off firstTemplatePass. If we don't set it here, instances will perform directive
|
|
|
|
// matching, etc again and again.
|
|
|
|
viewToRender[TVIEW].firstTemplatePass = false;
|
|
|
|
|
|
|
|
refreshDescendantViews(viewToRender);
|
2018-07-20 14:32:23 +02:00
|
|
|
} finally {
|
2018-12-18 16:58:51 -08:00
|
|
|
leaveView(oldView !);
|
2018-10-18 09:23:18 +02:00
|
|
|
setIsParent(_isParent);
|
|
|
|
setPreviousOrParentTNode(_previousOrParentTNode);
|
2018-04-12 13:49:37 +02:00
|
|
|
}
|
2018-01-17 10:09:05 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-17 11:45:49 -07:00
|
|
|
/**
|
2018-07-25 17:25:22 -07:00
|
|
|
* Retrieves a context at the level specified and saves it as the global, contextViewData.
|
|
|
|
* Will get the next level up if level is not specified.
|
2018-07-17 11:45:49 -07:00
|
|
|
*
|
2018-07-25 17:25:22 -07:00
|
|
|
* This is used to save contexts of parent views so they can be bound in embedded views, or
|
|
|
|
* in conjunction with reference() to bind a ref from a parent view.
|
2018-07-17 11:45:49 -07:00
|
|
|
*
|
2018-07-25 17:25:22 -07:00
|
|
|
* @param level The relative level of the view from which to grab context compared to contextVewData
|
|
|
|
* @returns context
|
2018-07-17 11:45:49 -07:00
|
|
|
*/
|
2018-07-30 19:43:56 -07:00
|
|
|
export function nextContext<T = any>(level: number = 1): T {
|
2018-10-18 09:23:18 +02:00
|
|
|
return nextContextImpl(level);
|
2018-07-17 11:45:49 -07:00
|
|
|
}
|
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
function renderComponentOrTemplate<T>(
|
2018-12-18 16:58:51 -08:00
|
|
|
hostView: LView, context: T, templateFn?: ComponentTemplate<T>) {
|
2018-11-21 21:14:06 -08:00
|
|
|
const rendererFactory = hostView[RENDERER_FACTORY];
|
2018-09-13 16:07:23 -07:00
|
|
|
const oldView = enterView(hostView, hostView[HOST_NODE]);
|
2018-12-21 11:53:18 -08:00
|
|
|
const normalExecutionPath = !getCheckNoChangesMode();
|
2019-01-16 12:22:10 -08:00
|
|
|
const creationModeIsActive = isCreationMode(hostView);
|
2017-12-01 14:23:03 -08:00
|
|
|
try {
|
2019-01-16 12:22:10 -08:00
|
|
|
if (normalExecutionPath && !creationModeIsActive && rendererFactory.begin) {
|
2017-12-11 16:30:46 +01:00
|
|
|
rendererFactory.begin();
|
|
|
|
}
|
2018-12-18 16:58:51 -08:00
|
|
|
|
2019-01-16 12:22:10 -08:00
|
|
|
if (creationModeIsActive) {
|
2018-12-18 16:58:51 -08:00
|
|
|
// creation mode pass
|
|
|
|
if (templateFn) {
|
|
|
|
namespaceHTML();
|
|
|
|
templateFn(RenderFlags.Create, context !);
|
|
|
|
}
|
|
|
|
|
|
|
|
refreshDescendantViews(hostView);
|
|
|
|
hostView[FLAGS] &= ~LViewFlags.CreationMode;
|
2017-12-11 16:30:46 +01:00
|
|
|
}
|
2018-12-18 16:58:51 -08:00
|
|
|
|
|
|
|
// update mode pass
|
|
|
|
templateFn && templateFn(RenderFlags.Update, context !);
|
|
|
|
refreshDescendantViews(hostView);
|
2017-12-01 14:23:03 -08:00
|
|
|
} finally {
|
2019-01-16 12:22:10 -08:00
|
|
|
if (normalExecutionPath && !creationModeIsActive && rendererFactory.end) {
|
2017-12-11 16:30:46 +01:00
|
|
|
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
|
2018-12-18 16:58:51 -08:00
|
|
|
* template is in creation mode or update mode. Update block and create block are
|
|
|
|
* always run separately.
|
2018-04-10 20:57:09 -07:00
|
|
|
*/
|
2018-11-21 21:14:06 -08:00
|
|
|
function getRenderFlags(view: LView): RenderFlags {
|
2018-12-18 16:58:51 -08:00
|
|
|
return isCreationMode(view) ? RenderFlags.Create : RenderFlags.Update;
|
2018-04-10 20:57:09 -07:00
|
|
|
}
|
|
|
|
|
2018-06-08 09:00:01 -07:00
|
|
|
//////////////////////////
|
|
|
|
//// Namespace
|
|
|
|
//////////////////////////
|
|
|
|
|
|
|
|
let _currentNamespace: string|null = null;
|
|
|
|
|
|
|
|
export function namespaceSVG() {
|
2018-12-06 16:39:43 -08:00
|
|
|
_currentNamespace = 'http://www.w3.org/2000/svg';
|
2018-06-08 09:00:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
export function namespaceMathML() {
|
|
|
|
_currentNamespace = 'http://www.w3.org/1998/MathML/';
|
|
|
|
}
|
|
|
|
|
|
|
|
export function namespaceHTML() {
|
|
|
|
_currentNamespace = null;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
//////////////////////////
|
|
|
|
|
2018-06-08 10:48:27 -07:00
|
|
|
/**
|
|
|
|
* Creates an empty element using {@link elementStart} and {@link elementEnd}
|
|
|
|
*
|
|
|
|
* @param index Index of the element in the data array
|
|
|
|
* @param name Name of the DOM Node
|
2018-12-13 15:51:47 -08:00
|
|
|
* @param attrs Statically bound set of attributes, classes, and styles to be written into the DOM
|
|
|
|
* element on creation. Use [AttributeMarker] to denote the meaning of this array.
|
2018-06-08 10:48:27 -07:00
|
|
|
* @param localRefs A set of local reference bindings on the element.
|
|
|
|
*/
|
|
|
|
export function element(
|
2018-07-27 14:56:15 -07:00
|
|
|
index: number, name: string, attrs?: TAttributes | null, localRefs?: string[] | null): void {
|
|
|
|
elementStart(index, name, attrs, localRefs);
|
2018-06-08 10:48:27 -07:00
|
|
|
elementEnd();
|
|
|
|
}
|
|
|
|
|
2018-07-26 17:22:41 +02:00
|
|
|
/**
|
|
|
|
* Creates a logical container for other nodes (<ng-container>) backed by a comment node in the DOM.
|
|
|
|
* The instruction must later be followed by `elementContainerEnd()` call.
|
|
|
|
*
|
2018-11-21 21:14:06 -08:00
|
|
|
* @param index Index of the element in the LView array
|
2018-07-26 17:22:41 +02:00
|
|
|
* @param attrs Set of attributes to be used when matching directives.
|
|
|
|
* @param localRefs A set of local reference bindings on the element.
|
|
|
|
*
|
2018-08-01 15:19:27 +02:00
|
|
|
* Even if this instruction accepts a set of attributes no actual attribute values are propagated to
|
2018-07-26 17:22:41 +02:00
|
|
|
* the DOM (as a comment node can't have attributes). Attributes are here only for directive
|
|
|
|
* matching purposes and setting initial inputs of directives.
|
|
|
|
*/
|
|
|
|
export function elementContainerStart(
|
|
|
|
index: number, attrs?: TAttributes | null, localRefs?: string[] | null): void {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const tView = lView[TVIEW];
|
|
|
|
const renderer = lView[RENDERER];
|
2018-12-12 15:23:12 -08:00
|
|
|
const tagName = 'ng-container';
|
2018-08-16 18:53:21 -07:00
|
|
|
ngDevMode && assertEqual(
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[BINDING_INDEX], tView.bindingStartIndex,
|
2018-08-16 18:53:21 -07:00
|
|
|
'element containers should be created before any bindings');
|
2018-07-26 17:22:41 +02:00
|
|
|
|
|
|
|
ngDevMode && ngDevMode.rendererCreateComment++;
|
2018-12-12 15:23:12 -08:00
|
|
|
const native = renderer.createComment(ngDevMode ? tagName : '');
|
2018-07-26 17:22:41 +02:00
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
ngDevMode && assertDataInRange(lView, index - 1);
|
2018-12-12 15:23:12 -08:00
|
|
|
const tNode =
|
|
|
|
createNodeAtIndex(index, TNodeType.ElementContainer, native, tagName, attrs || null);
|
2018-07-26 17:22:41 +02:00
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
appendChild(native, tNode, lView);
|
|
|
|
createDirectivesAndLocals(tView, lView, localRefs);
|
2018-12-17 14:19:25 +01:00
|
|
|
attachPatchData(native, lView);
|
2019-01-23 16:58:52 +01:00
|
|
|
|
|
|
|
const currentQueries = lView[QUERIES];
|
|
|
|
if (currentQueries) {
|
|
|
|
currentQueries.addNode(tNode);
|
|
|
|
}
|
2018-07-26 17:22:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Mark the end of the <ng-container>. */
|
|
|
|
export function elementContainerEnd(): void {
|
2018-10-18 09:23:18 +02:00
|
|
|
let previousOrParentTNode = getPreviousOrParentTNode();
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const tView = lView[TVIEW];
|
2018-10-18 09:23:18 +02:00
|
|
|
if (getIsParent()) {
|
|
|
|
setIsParent(false);
|
2018-07-26 17:22:41 +02:00
|
|
|
} else {
|
2018-11-21 21:14:06 -08:00
|
|
|
ngDevMode && assertHasParent(getPreviousOrParentTNode());
|
2018-09-05 16:15:37 -07:00
|
|
|
previousOrParentTNode = previousOrParentTNode.parent !;
|
2018-10-18 09:23:18 +02:00
|
|
|
setPreviousOrParentTNode(previousOrParentTNode);
|
2018-07-26 17:22:41 +02:00
|
|
|
}
|
|
|
|
|
2018-09-05 16:15:37 -07:00
|
|
|
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.ElementContainer);
|
2018-11-21 21:14:06 -08:00
|
|
|
const currentQueries = lView[QUERIES];
|
2018-10-18 09:23:18 +02:00
|
|
|
if (currentQueries) {
|
2019-01-23 16:58:52 +01:00
|
|
|
lView[QUERIES] =
|
|
|
|
isContentQueryHost(previousOrParentTNode) ? currentQueries.parent : currentQueries;
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
2018-08-09 10:00:07 -07:00
|
|
|
|
2018-12-20 17:23:25 -08:00
|
|
|
registerPostOrderHooks(tView, previousOrParentTNode);
|
2018-07-26 17:22:41 +02:00
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
|
|
|
* Create DOM element. The instruction must later be followed by `elementEnd()` call.
|
|
|
|
*
|
2018-11-21 21:14:06 -08:00
|
|
|
* @param index Index of the element in the LView array
|
2018-03-25 21:32:39 -07:00
|
|
|
* @param name Name of the DOM Node
|
2018-12-13 15:51:47 -08:00
|
|
|
* @param attrs Statically bound set of attributes, classes, and styles to be written into the DOM
|
|
|
|
* element on creation. Use [AttributeMarker] to denote the meaning of this array.
|
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-07-27 14:56:15 -07:00
|
|
|
index: number, name: string, attrs?: TAttributes | null, localRefs?: string[] | null): void {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const tView = lView[TVIEW];
|
2018-08-16 18:53:21 -07:00
|
|
|
ngDevMode && assertEqual(
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[BINDING_INDEX], tView.bindingStartIndex,
|
2018-08-16 18:53:21 -07:00
|
|
|
'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-08 09:00:01 -07:00
|
|
|
|
2018-07-20 14:32:23 +02:00
|
|
|
const native = elementCreate(name);
|
2018-06-08 09:00:01 -07:00
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
ngDevMode && assertDataInRange(lView, index - 1);
|
2018-05-11 20:57:37 -07:00
|
|
|
|
2018-10-12 18:49:00 -07:00
|
|
|
const tNode = createNodeAtIndex(index, TNodeType.Element, native !, name, attrs || null);
|
2018-03-21 15:10:34 -07:00
|
|
|
|
2018-06-19 12:45:00 -07:00
|
|
|
if (attrs) {
|
2018-12-13 15:51:47 -08:00
|
|
|
// it's important to only prepare styling-related datastructures once for a given
|
|
|
|
// tNode and not each time an element is created. Also, the styling code is designed
|
|
|
|
// to be patched and constructed at various points, but only up until the first element
|
|
|
|
// is created. Then the styling context is locked and can only be instantiated for each
|
|
|
|
// successive element that is created.
|
|
|
|
if (tView.firstTemplatePass && !tNode.stylingTemplate && hasStyling(attrs)) {
|
|
|
|
tNode.stylingTemplate = initializeStaticStylingContext(attrs);
|
|
|
|
}
|
2018-06-19 12:45:00 -07:00
|
|
|
setUpAttributes(native, attrs);
|
|
|
|
}
|
2018-10-12 18:49:00 -07:00
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
appendChild(native, tNode, lView);
|
|
|
|
createDirectivesAndLocals(tView, lView, localRefs);
|
2018-08-22 16:57:40 -07:00
|
|
|
|
|
|
|
// any immediate children of a component or template container must be pre-emptively
|
|
|
|
// monkey-patched with the component view data so that the element can be inspected
|
|
|
|
// later on using any element discovery utility methods (see `element_discovery.ts`)
|
2018-10-18 09:23:18 +02:00
|
|
|
if (getElementDepthCount() === 0) {
|
2018-11-21 21:14:06 -08:00
|
|
|
attachPatchData(native, lView);
|
2018-08-22 16:57:40 -07:00
|
|
|
}
|
2018-10-18 09:23:18 +02:00
|
|
|
increaseElementDepthCount();
|
2018-12-13 15:51:47 -08:00
|
|
|
|
|
|
|
// if a directive contains a host binding for "class" then all class-based data will
|
|
|
|
// flow through that (except for `[class.prop]` bindings). This also includes initial
|
|
|
|
// static class values as well. (Note that this will be fixed once map-based `[style]`
|
|
|
|
// and `[class]` bindings work for multiple directives.)
|
|
|
|
if (tView.firstTemplatePass) {
|
|
|
|
const inputData = initializeTNodeInputs(tNode);
|
|
|
|
if (inputData && inputData.hasOwnProperty('class')) {
|
|
|
|
tNode.flags |= TNodeFlags.hasClassInput;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// There is no point in rendering styles when a class directive is present since
|
|
|
|
// it will take that over for us (this will be removed once #FW-882 is in).
|
|
|
|
if (tNode.stylingTemplate && (tNode.flags & TNodeFlags.hasClassInput) === 0) {
|
|
|
|
renderInitialStylesAndClasses(native, tNode.stylingTemplate, lView[RENDERER]);
|
|
|
|
}
|
2019-01-23 16:58:52 +01:00
|
|
|
|
|
|
|
const currentQueries = lView[QUERIES];
|
|
|
|
if (currentQueries) {
|
|
|
|
currentQueries.addNode(tNode);
|
|
|
|
}
|
2018-04-04 21:21:12 -07:00
|
|
|
}
|
2018-07-27 14:56:15 -07:00
|
|
|
|
2018-07-20 14:32:23 +02:00
|
|
|
/**
|
|
|
|
* Creates a native element from a tag name, using a renderer.
|
|
|
|
* @param name the tag name
|
|
|
|
* @param overriddenRenderer Optional A renderer to override the default one
|
|
|
|
* @returns the element created
|
|
|
|
*/
|
|
|
|
export function elementCreate(name: string, overriddenRenderer?: Renderer3): RElement {
|
|
|
|
let native: RElement;
|
2018-11-21 21:14:06 -08:00
|
|
|
const rendererToUse = overriddenRenderer || getLView()[RENDERER];
|
2018-07-20 14:32:23 +02:00
|
|
|
|
|
|
|
if (isProceduralRenderer(rendererToUse)) {
|
|
|
|
native = rendererToUse.createElement(name, _currentNamespace);
|
|
|
|
} else {
|
|
|
|
if (_currentNamespace === null) {
|
|
|
|
native = rendererToUse.createElement(name);
|
|
|
|
} else {
|
|
|
|
native = rendererToUse.createElementNS(_currentNamespace, name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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.
|
|
|
|
*
|
2018-08-14 16:25:01 +02:00
|
|
|
* @param localRefs Local refs of the node in question
|
2018-10-12 18:49:00 -07:00
|
|
|
* @param localRefExtractor mapping function that extracts local ref value from TNode
|
2018-04-26 10:44:49 -07:00
|
|
|
*/
|
2018-08-14 16:25:01 +02:00
|
|
|
function createDirectivesAndLocals(
|
2019-01-02 19:29:08 +01:00
|
|
|
tView: TView, lView: LView, localRefs: string[] | null | undefined,
|
2018-10-12 18:49:00 -07:00
|
|
|
localRefExtractor: LocalRefExtractor = getNativeByTNode) {
|
2018-10-18 09:23:18 +02:00
|
|
|
if (!getBindingsEnabled()) return;
|
|
|
|
const previousOrParentTNode = getPreviousOrParentTNode();
|
2019-01-30 16:38:59 +01:00
|
|
|
if (tView.firstTemplatePass) {
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.firstTemplatePass++;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
resolveDirectives(
|
2019-01-02 19:29:08 +01:00
|
|
|
tView, lView, findDirectiveMatches(tView, lView, previousOrParentTNode),
|
2018-10-18 09:23:18 +02:00
|
|
|
previousOrParentTNode, localRefs || null);
|
2019-01-02 19:29:08 +01:00
|
|
|
} else {
|
|
|
|
// During first template pass, queries are created or cloned when first requested
|
|
|
|
// using `getOrCreateCurrentQueries`. For subsequent template passes, we clone
|
|
|
|
// any current LQueries here up-front if the current node hosts a content query.
|
|
|
|
if (isContentQueryHost(getPreviousOrParentTNode()) && lView[QUERIES]) {
|
|
|
|
lView[QUERIES] = lView[QUERIES] !.clone();
|
|
|
|
}
|
2018-03-27 11:01:52 -07:00
|
|
|
}
|
2019-01-02 19:29:08 +01:00
|
|
|
instantiateAllDirectives(tView, lView, previousOrParentTNode);
|
|
|
|
invokeDirectivesHostBindings(tView, lView, previousOrParentTNode);
|
|
|
|
saveResolvedLocalsInData(lView, previousOrParentTNode, localRefExtractor);
|
2018-03-27 11:01:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Takes a list of local names and indices and pushes the resolved local variable values
|
2018-11-21 21:14:06 -08:00
|
|
|
* to LView in the same order as they are loaded in the template with load().
|
2018-03-27 11:01:52 -07:00
|
|
|
*/
|
2018-10-18 09:23:18 +02:00
|
|
|
function saveResolvedLocalsInData(
|
2018-11-21 21:14:06 -08:00
|
|
|
viewData: LView, tNode: TNode, localRefExtractor: LocalRefExtractor): void {
|
2018-10-18 09:23:18 +02:00
|
|
|
const localNames = tNode.localNames;
|
2018-03-27 11:01:52 -07:00
|
|
|
if (localNames) {
|
2018-10-18 09:23:18 +02:00
|
|
|
let localIndex = tNode.index + 1;
|
2018-03-27 11:01:52 -07:00
|
|
|
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-10-18 09:23:18 +02:00
|
|
|
const value = index === -1 ?
|
|
|
|
localRefExtractor(
|
|
|
|
tNode as TElementNode | TContainerNode | TElementContainerNode, viewData) :
|
|
|
|
viewData[index];
|
2018-08-21 00:03:21 -07:00
|
|
|
viewData[localIndex++] = 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
|
|
|
*
|
2018-08-15 18:37:03 -07:00
|
|
|
* @param templateFn The template from which to get static data
|
2018-08-18 11:14:50 -07:00
|
|
|
* @param consts The number of nodes, local refs, and pipes in this view
|
|
|
|
* @param vars The number of bindings and pure function bindings in this view
|
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-10-12 15:02:54 -07:00
|
|
|
export function getOrCreateTView(
|
2018-08-18 11:14:50 -07:00
|
|
|
templateFn: ComponentTemplate<any>, consts: number, vars: number,
|
2018-08-16 18:53:21 -07:00
|
|
|
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
|
|
|
|
viewQuery: ComponentQuery<any>| 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-08-16 18:53:21 -07:00
|
|
|
return templateFn.ngPrivateData ||
|
|
|
|
(templateFn.ngPrivateData =
|
2018-08-18 11:14:50 -07:00
|
|
|
createTView(-1, templateFn, consts, vars, directives, pipes, viewQuery) 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
|
2018-08-16 18:53:21 -07:00
|
|
|
* @param templateFn Template function
|
|
|
|
* @param consts The number of nodes, local refs, and pipes in this template
|
2018-06-01 19:28:20 -07:00
|
|
|
* @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-08-18 11:14:50 -07:00
|
|
|
viewIndex: number, templateFn: ComponentTemplate<any>| null, consts: number, vars: number,
|
2018-06-19 17:58:42 +02:00
|
|
|
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
|
|
|
|
viewQuery: ComponentQuery<any>| null): TView {
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.tView++;
|
2018-08-20 13:03:03 -07:00
|
|
|
const bindingStartIndex = HEADER_OFFSET + consts;
|
2018-08-21 00:03:21 -07:00
|
|
|
// This length does not yet contain host bindings from child directives because at this point,
|
|
|
|
// we don't know which directives are active on this template. As soon as a directive is matched
|
|
|
|
// that has a host binding, we will update the blueprint with that def's hostVars count.
|
|
|
|
const initialViewLength = bindingStartIndex + vars;
|
|
|
|
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
|
2018-11-14 19:25:03 -08:00
|
|
|
return blueprint[TVIEW as any] = {
|
2018-06-01 19:28:20 -07:00
|
|
|
id: viewIndex,
|
2018-08-21 00:03:21 -07:00
|
|
|
blueprint: blueprint,
|
2018-08-15 18:37:03 -07:00
|
|
|
template: templateFn,
|
2018-06-19 17:58:42 +02:00
|
|
|
viewQuery: viewQuery,
|
2018-05-16 05:56:01 -07:00
|
|
|
node: null !,
|
2019-01-24 08:53:00 -08:00
|
|
|
data: blueprint.slice().fill(null, bindingStartIndex),
|
|
|
|
childIndex: -1, // Children set in addToViewTree(), if any
|
2018-08-20 13:03:03 -07:00
|
|
|
bindingStartIndex: bindingStartIndex,
|
2019-01-18 18:02:32 -08:00
|
|
|
viewQueryStartIndex: initialViewLength,
|
2018-10-08 16:04:46 -07:00
|
|
|
expandoStartIndex: initialViewLength,
|
|
|
|
expandoInstructions: 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-06-05 15:28:15 -07:00
|
|
|
cleanup: null,
|
2018-07-10 10:43:07 +02:00
|
|
|
contentQueries: 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,
|
2018-08-29 13:52:03 -07:00
|
|
|
firstChild: null,
|
2018-01-23 10:57:48 -08:00
|
|
|
};
|
2017-12-11 14:08:52 -08:00
|
|
|
}
|
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
function createViewBlueprint(bindingStartIndex: number, initialViewLength: number): LView {
|
2018-08-21 00:03:21 -07:00
|
|
|
const blueprint = new Array(initialViewLength)
|
|
|
|
.fill(null, 0, bindingStartIndex)
|
2018-11-21 21:14:06 -08:00
|
|
|
.fill(NO_CHANGE, bindingStartIndex) as LView;
|
2018-08-21 00:03:21 -07:00
|
|
|
blueprint[CONTAINER_INDEX] = -1;
|
|
|
|
blueprint[BINDING_INDEX] = bindingStartIndex;
|
|
|
|
return blueprint;
|
|
|
|
}
|
|
|
|
|
2019-01-11 14:03:37 -08:00
|
|
|
/**
|
|
|
|
* Assigns all attribute values to the provided element via the inferred renderer.
|
|
|
|
*
|
|
|
|
* This function accepts two forms of attribute entries:
|
|
|
|
*
|
|
|
|
* default: (key, value):
|
|
|
|
* attrs = [key1, value1, key2, value2]
|
|
|
|
*
|
|
|
|
* namespaced: (NAMESPACE_MARKER, uri, name, value)
|
|
|
|
* attrs = [NAMESPACE_MARKER, uri, name, value, NAMESPACE_MARKER, uri, name, value]
|
|
|
|
*
|
|
|
|
* The `attrs` array can contain a mix of both the default and namespaced entries.
|
|
|
|
* The "default" values are set without a marker, but if the function comes across
|
|
|
|
* a marker value then it will attempt to set a namespaced value. If the marker is
|
|
|
|
* not of a namespaced value then the function will quit and return the index value
|
|
|
|
* where it stopped during the iteration of the attrs array.
|
|
|
|
*
|
|
|
|
* See [AttributeMarker] to understand what the namespace marker value is.
|
|
|
|
*
|
|
|
|
* Note that this instruction does not support assigning style and class values to
|
|
|
|
* an element. See `elementStart` and `elementHostAttrs` to learn how styling values
|
|
|
|
* are applied to an element.
|
|
|
|
*
|
|
|
|
* @param native The element that the attributes will be assigned to
|
|
|
|
* @param attrs The attribute array of values that will be assigned to the element
|
|
|
|
* @returns the index value that was last accessed in the attributes array
|
|
|
|
*/
|
|
|
|
function setUpAttributes(native: RElement, attrs: TAttributes): number {
|
2018-11-21 21:14:06 -08:00
|
|
|
const renderer = getLView()[RENDERER];
|
2018-02-07 22:57:11 -08:00
|
|
|
const isProc = isProceduralRenderer(renderer);
|
2018-06-08 15:25:39 -07:00
|
|
|
|
2019-01-11 14:03:37 -08:00
|
|
|
let i = 0;
|
2018-06-08 15:25:39 -07:00
|
|
|
while (i < attrs.length) {
|
2019-01-11 14:03:37 -08:00
|
|
|
const value = attrs[i];
|
|
|
|
if (typeof value === 'number') {
|
|
|
|
// only namespaces are supported. Other value types (such as style/class
|
|
|
|
// entries) are not supported in this function.
|
|
|
|
if (value !== AttributeMarker.NamespaceURI) {
|
2018-12-13 15:51:47 -08:00
|
|
|
break;
|
|
|
|
}
|
2019-01-11 14:03:37 -08:00
|
|
|
|
|
|
|
// we just landed on the marker value ... therefore
|
|
|
|
// we should skip to the next entry
|
|
|
|
i++;
|
|
|
|
|
|
|
|
const namespaceURI = attrs[i++] as string;
|
|
|
|
const attrName = attrs[i++] as string;
|
|
|
|
const attrVal = attrs[i++] as string;
|
|
|
|
ngDevMode && ngDevMode.rendererSetAttribute++;
|
|
|
|
isProc ?
|
|
|
|
(renderer as ProceduralRenderer3).setAttribute(native, attrName, attrVal, namespaceURI) :
|
|
|
|
native.setAttributeNS(namespaceURI, attrName, attrVal);
|
2018-12-13 15:51:47 -08:00
|
|
|
} else {
|
|
|
|
/// attrName is string;
|
2019-01-11 14:03:37 -08:00
|
|
|
const attrName = value as string;
|
|
|
|
const attrVal = attrs[++i];
|
2018-12-13 15:51:47 -08:00
|
|
|
if (attrName !== NG_PROJECT_AS_ATTR_NAME) {
|
2018-06-08 15:25:39 -07:00
|
|
|
// Standard attributes
|
2018-12-13 15:51:47 -08:00
|
|
|
ngDevMode && ngDevMode.rendererSetAttribute++;
|
2018-12-05 15:56:36 -08:00
|
|
|
if (isAnimationProp(attrName)) {
|
|
|
|
if (isProc) {
|
|
|
|
(renderer as ProceduralRenderer3).setProperty(native, attrName, attrVal);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
isProc ?
|
|
|
|
(renderer as ProceduralRenderer3)
|
|
|
|
.setAttribute(native, attrName as string, attrVal as string) :
|
|
|
|
native.setAttribute(attrName as string, attrVal as string);
|
|
|
|
}
|
2018-06-08 15:25:39 -07:00
|
|
|
}
|
2019-01-11 14:03:37 -08:00
|
|
|
i++;
|
2018-02-28 15:00:58 +01:00
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2019-01-11 14:03:37 -08:00
|
|
|
|
|
|
|
// another piece of code may iterate over the same attributes array. Therefore
|
|
|
|
// it may be helpful to return the exact spot where the attributes array exited
|
|
|
|
// whether by running into an unsupported marker or if all the static values were
|
|
|
|
// iterated over.
|
|
|
|
return i;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function createError(text: string, token: any) {
|
2019-01-12 00:59:48 -08:00
|
|
|
return new Error(`Renderer: ${text} [${renderStringify(token)}]`);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
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 {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
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
|
2018-12-19 15:03:47 -08:00
|
|
|
* @param useCapture Whether or not to use capture in event listener
|
|
|
|
* @param eventTargetResolver Function that returns global target information in case this listener
|
|
|
|
* should be attached to a global object like window, document or body
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-03-01 09:46:39 -08:00
|
|
|
export function listener(
|
2018-12-19 15:03:47 -08:00
|
|
|
eventName: string, listenerFn: (e?: any) => any, useCapture = false,
|
|
|
|
eventTargetResolver?: GlobalTargetResolver): void {
|
2019-01-17 11:09:13 -08:00
|
|
|
listenerInternal(eventName, listenerFn, useCapture, eventTargetResolver);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a synthetic host listener (e.g. `(@foo.start)`) on a component.
|
|
|
|
*
|
|
|
|
* This instruction is for compatibility purposes and is designed to ensure that a
|
|
|
|
* synthetic host listener (e.g. `@HostListener('@foo.start')`) properly gets rendered
|
|
|
|
* in the component's renderer. Normally all host listeners are evaluated with the
|
|
|
|
* parent component's renderer, but, in the case of animation @triggers, they need
|
|
|
|
* to be evaluated with the sub component's renderer (because that's where the
|
|
|
|
* animation triggers are defined).
|
|
|
|
*
|
|
|
|
* Do not use this instruction as a replacement for `listener`. This instruction
|
|
|
|
* only exists to ensure compatibility with the ViewEngine's host binding behavior.
|
|
|
|
*
|
|
|
|
* @param eventName Name of the event
|
|
|
|
* @param listenerFn The function to be called when event emits
|
|
|
|
* @param useCapture Whether or not to use capture in event listener
|
|
|
|
* @param eventTargetResolver Function that returns global target information in case this listener
|
|
|
|
* should be attached to a global object like window, document or body
|
|
|
|
*/
|
|
|
|
export function componentHostSyntheticListener<T>(
|
|
|
|
eventName: string, listenerFn: (e?: any) => any, useCapture = false,
|
|
|
|
eventTargetResolver?: GlobalTargetResolver): void {
|
|
|
|
listenerInternal(eventName, listenerFn, useCapture, eventTargetResolver, loadComponentRenderer);
|
|
|
|
}
|
|
|
|
|
|
|
|
function listenerInternal(
|
|
|
|
eventName: string, listenerFn: (e?: any) => any, useCapture = false,
|
|
|
|
eventTargetResolver?: GlobalTargetResolver,
|
|
|
|
loadRendererFn?: ((tNode: TNode, lView: LView) => Renderer3) | null): void {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
2018-10-18 09:23:18 +02:00
|
|
|
const tNode = getPreviousOrParentTNode();
|
2018-11-28 15:54:38 -08:00
|
|
|
const tView = lView[TVIEW];
|
|
|
|
const firstTemplatePass = tView.firstTemplatePass;
|
|
|
|
const tCleanup: false|any[] = firstTemplatePass && (tView.cleanup || (tView.cleanup = []));
|
2018-09-05 16:15:37 -07:00
|
|
|
ngDevMode && assertNodeOfPossibleTypes(
|
|
|
|
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
|
2018-08-28 15:31:09 +02:00
|
|
|
|
|
|
|
// add native event listener - applicable to elements only
|
2018-09-05 16:15:37 -07:00
|
|
|
if (tNode.type === TNodeType.Element) {
|
2018-11-21 21:14:06 -08:00
|
|
|
const native = getNativeByTNode(tNode, lView) as RElement;
|
2018-12-19 15:03:47 -08:00
|
|
|
const resolved = eventTargetResolver ? eventTargetResolver(native) : {} as any;
|
|
|
|
const target = resolved.target || native;
|
2018-08-28 15:31:09 +02:00
|
|
|
ngDevMode && ngDevMode.rendererAddEventListener++;
|
2019-01-17 11:09:13 -08:00
|
|
|
const renderer = loadRendererFn ? loadRendererFn(tNode, lView) : lView[RENDERER];
|
2018-11-28 15:54:38 -08:00
|
|
|
const lCleanup = getCleanup(lView);
|
|
|
|
const lCleanupIndex = lCleanup.length;
|
|
|
|
let useCaptureOrSubIdx: boolean|number = useCapture;
|
2018-08-28 15:31:09 +02:00
|
|
|
|
|
|
|
// In order to match current behavior, native DOM event listeners must be added for all
|
|
|
|
// events (including outputs).
|
|
|
|
if (isProceduralRenderer(renderer)) {
|
2018-12-19 15:03:47 -08:00
|
|
|
// The first argument of `listen` function in Procedural Renderer is:
|
|
|
|
// - either a target name (as a string) in case of global target (window, document, body)
|
|
|
|
// - or element reference (in all other cases)
|
|
|
|
const cleanupFn = renderer.listen(resolved.name || target, eventName, listenerFn);
|
2018-11-28 15:54:38 -08:00
|
|
|
lCleanup.push(listenerFn, cleanupFn);
|
|
|
|
useCaptureOrSubIdx = lCleanupIndex + 1;
|
2018-08-28 15:31:09 +02:00
|
|
|
} else {
|
2018-09-14 15:58:57 -07:00
|
|
|
const wrappedListener = wrapListenerWithPreventDefault(listenerFn);
|
2018-12-19 15:03:47 -08:00
|
|
|
target.addEventListener(eventName, wrappedListener, useCapture);
|
2018-11-28 15:54:38 -08:00
|
|
|
lCleanup.push(wrappedListener);
|
2018-06-05 15:28:15 -07:00
|
|
|
}
|
2018-12-19 15:03:47 -08:00
|
|
|
|
|
|
|
const idxOrTargetGetter = eventTargetResolver ?
|
|
|
|
(_lView: LView) => eventTargetResolver(readElementValue(_lView[tNode.index])).target :
|
|
|
|
tNode.index;
|
|
|
|
tCleanup && tCleanup.push(eventName, idxOrTargetGetter, lCleanupIndex, useCaptureOrSubIdx);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-08-28 15:31:09 +02:00
|
|
|
// subscribe to directive outputs
|
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-11-28 15:54:38 -08:00
|
|
|
tNode.outputs = generatePropertyAliases(tNode, BindingDirection.Output);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-01-08 20:17:13 -08:00
|
|
|
const outputs = tNode.outputs;
|
2018-11-28 15:54:38 -08:00
|
|
|
let props: PropertyAliasValue|undefined;
|
|
|
|
if (outputs && (props = outputs[eventName])) {
|
|
|
|
const propsLength = props.length;
|
|
|
|
if (propsLength) {
|
|
|
|
const lCleanup = getCleanup(lView);
|
2019-01-16 09:35:35 -08:00
|
|
|
for (let i = 0; i < propsLength; i += 3) {
|
2019-01-14 17:39:21 -08:00
|
|
|
const index = props[i] as number;
|
|
|
|
ngDevMode && assertDataInRange(lView, index);
|
2019-01-16 09:35:35 -08:00
|
|
|
const minifiedName = props[i + 2];
|
2019-01-14 17:39:21 -08:00
|
|
|
const directiveInstance = lView[index];
|
|
|
|
const output = directiveInstance[minifiedName];
|
2019-01-11 21:14:42 +01:00
|
|
|
|
|
|
|
if (ngDevMode && !isObservable(output)) {
|
|
|
|
throw new Error(
|
2019-01-14 17:39:21 -08:00
|
|
|
`@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`);
|
2019-01-11 21:14:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const subscription = output.subscribe(listenerFn);
|
2018-11-28 15:54:38 -08:00
|
|
|
const idx = lCleanup.length;
|
|
|
|
lCleanup.push(listenerFn, subscription);
|
|
|
|
tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
|
|
|
|
}
|
|
|
|
}
|
2018-06-05 15:28:15 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2018-11-21 21:14:06 -08:00
|
|
|
export function storeCleanupWithContext(lView: LView, context: any, cleanupFn: Function): void {
|
2018-11-28 15:54:38 -08:00
|
|
|
const lCleanup = getCleanup(lView);
|
|
|
|
lCleanup.push(context);
|
2018-06-05 15:28:15 -07:00
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
if (lView[TVIEW].firstTemplatePass) {
|
2018-11-28 15:54:38 -08:00
|
|
|
getTViewCleanup(lView).push(cleanupFn, lCleanup.length - 1);
|
2018-06-05 15:28:15 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2018-11-21 21:14:06 -08:00
|
|
|
export function storeCleanupFn(view: LView, cleanupFn: Function): void {
|
2018-06-05 15:28:15 -07:00
|
|
|
getCleanup(view).push(cleanupFn);
|
|
|
|
|
2018-06-07 22:42:32 -07:00
|
|
|
if (view[TVIEW].firstTemplatePass) {
|
|
|
|
getTViewCleanup(view).push(view[CLEANUP] !.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. */
|
2018-07-27 14:28:22 -07:00
|
|
|
export function elementEnd(): void {
|
2018-10-18 09:23:18 +02:00
|
|
|
let previousOrParentTNode = getPreviousOrParentTNode();
|
|
|
|
if (getIsParent()) {
|
|
|
|
setIsParent(false);
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2018-11-21 21:14:06 -08:00
|
|
|
ngDevMode && assertHasParent(getPreviousOrParentTNode());
|
2018-09-05 16:15:37 -07:00
|
|
|
previousOrParentTNode = previousOrParentTNode.parent !;
|
2018-10-18 09:23:18 +02:00
|
|
|
setPreviousOrParentTNode(previousOrParentTNode);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-09-05 16:15:37 -07:00
|
|
|
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Element);
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const currentQueries = lView[QUERIES];
|
2018-10-18 09:23:18 +02:00
|
|
|
if (currentQueries) {
|
2019-01-23 16:58:52 +01:00
|
|
|
lView[QUERIES] =
|
|
|
|
isContentQueryHost(previousOrParentTNode) ? currentQueries.parent : currentQueries;
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
2018-09-05 16:15:37 -07:00
|
|
|
|
2018-12-20 17:23:25 -08:00
|
|
|
registerPostOrderHooks(getLView()[TVIEW], previousOrParentTNode);
|
2018-10-18 09:23:18 +02:00
|
|
|
decreaseElementDepthCount();
|
2018-12-13 15:51:47 -08:00
|
|
|
|
|
|
|
// this is fired at the end of elementEnd because ALL of the stylingBindings code
|
|
|
|
// (for directives and the template) have now executed which means the styling
|
|
|
|
// context can be instantiated properly.
|
|
|
|
if (hasClassInput(previousOrParentTNode)) {
|
|
|
|
const stylingContext = getStylingContext(previousOrParentTNode.index, lView);
|
|
|
|
setInputsForProperty(
|
2019-01-14 17:39:21 -08:00
|
|
|
lView, previousOrParentTNode.inputs !['class'] !, getInitialClassNameValue(stylingContext));
|
2018-12-13 15:51:47 -08:00
|
|
|
}
|
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.
|
2019-01-22 23:21:53 +01:00
|
|
|
* @param namespace Optional namespace to use when setting the attribute.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-03-01 17:14:01 -08:00
|
|
|
export function elementAttribute(
|
2019-01-22 23:21:53 +01:00
|
|
|
index: number, name: string, value: any, sanitizer?: SanitizerFn | null,
|
|
|
|
namespace?: string): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
if (value !== NO_CHANGE) {
|
2019-01-10 13:34:39 -08:00
|
|
|
ngDevMode && validateAttribute(name);
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const renderer = lView[RENDERER];
|
|
|
|
const element = getNativeByIndex(index, lView);
|
2017-12-01 14:23:03 -08:00
|
|
|
if (value == null) {
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererRemoveAttribute++;
|
2019-01-22 23:21:53 +01:00
|
|
|
isProceduralRenderer(renderer) ? renderer.removeAttribute(element, name, namespace) :
|
2018-10-12 18:49:00 -07:00
|
|
|
element.removeAttribute(name);
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererSetAttribute++;
|
2019-01-03 10:04:06 -08:00
|
|
|
const tNode = getTNode(index, lView);
|
|
|
|
const strValue =
|
2019-01-12 00:59:48 -08:00
|
|
|
sanitizer == null ? renderStringify(value) : sanitizer(value, tNode.tagName || '', name);
|
2019-01-22 23:21:53 +01:00
|
|
|
|
|
|
|
|
|
|
|
if (isProceduralRenderer(renderer)) {
|
|
|
|
renderer.setAttribute(element, name, strValue, namespace);
|
|
|
|
} else {
|
|
|
|
namespace ? element.setAttributeNS(namespace, name, strValue) :
|
|
|
|
element.setAttribute(name, strValue);
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-12-10 14:51:28 -08:00
|
|
|
* Update a property on an element.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
|
|
|
* 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.
|
2018-12-10 14:51:28 -08:00
|
|
|
* @param nativeOnly Whether or not we should only set native properties and skip input check
|
|
|
|
* (this is necessary for host property bindings)
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-03-01 17:14:01 -08:00
|
|
|
export function elementProperty<T>(
|
2018-12-10 14:51:28 -08:00
|
|
|
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn | null,
|
|
|
|
nativeOnly?: boolean): void {
|
2019-01-03 18:24:21 -08:00
|
|
|
elementPropertyInternal(index, propName, value, sanitizer, nativeOnly);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates a synthetic host binding (e.g. `[@foo]`) on a component.
|
|
|
|
*
|
|
|
|
* This instruction is for compatibility purposes and is designed to ensure that a
|
|
|
|
* synthetic host binding (e.g. `@HostBinding('@foo')`) properly gets rendered in
|
|
|
|
* the component's renderer. Normally all host bindings are evaluated with the parent
|
|
|
|
* component's renderer, but, in the case of animation @triggers, they need to be
|
2019-01-17 11:09:13 -08:00
|
|
|
* evaluated with the sub component's renderer (because that's where the animation
|
2019-01-03 18:24:21 -08:00
|
|
|
* triggers are defined).
|
|
|
|
*
|
|
|
|
* Do not use this instruction as a replacement for `elementProperty`. This instruction
|
|
|
|
* only exists to ensure compatibility with the ViewEngine's host binding behavior.
|
|
|
|
*
|
|
|
|
* @param index The index of the element to update in the data array
|
|
|
|
* @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.
|
|
|
|
* @param sanitizer An optional function used to sanitize the value.
|
|
|
|
* @param nativeOnly Whether or not we should only set native properties and skip input check
|
|
|
|
* (this is necessary for host property bindings)
|
|
|
|
*/
|
|
|
|
export function componentHostSyntheticProperty<T>(
|
|
|
|
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn | null,
|
|
|
|
nativeOnly?: boolean) {
|
|
|
|
elementPropertyInternal(index, propName, value, sanitizer, nativeOnly, loadComponentRenderer);
|
|
|
|
}
|
|
|
|
|
|
|
|
function elementPropertyInternal<T>(
|
|
|
|
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn | null,
|
|
|
|
nativeOnly?: boolean,
|
|
|
|
loadRendererFn?: ((tNode: TNode, lView: LView) => Renderer3) | null): void {
|
2017-12-01 14:23:03 -08:00
|
|
|
if (value === NO_CHANGE) return;
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const element = getNativeByIndex(index, lView) as RElement | RComment;
|
|
|
|
const tNode = getTNode(index, lView);
|
2018-12-10 14:51:28 -08:00
|
|
|
let inputData: PropertyAliases|null|undefined;
|
2018-02-07 22:19:24 -08:00
|
|
|
let dataValue: PropertyAliasValue|undefined;
|
2018-12-10 14:51:28 -08:00
|
|
|
if (!nativeOnly && (inputData = initializeTNodeInputs(tNode)) &&
|
|
|
|
(dataValue = inputData[propName])) {
|
2019-01-14 17:39:21 -08:00
|
|
|
setInputsForProperty(lView, dataValue, value);
|
2018-11-21 21:14:06 -08:00
|
|
|
if (isComponent(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
|
2018-11-29 16:39:43 +01:00
|
|
|
if (ngDevMode) {
|
|
|
|
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.Container) {
|
2018-11-30 17:45:04 +01:00
|
|
|
setNgReflectProperties(lView, element, tNode.type, dataValue, value);
|
2018-11-29 16:39:43 +01:00
|
|
|
}
|
2018-11-21 13:43:00 +01:00
|
|
|
}
|
2018-10-11 13:13:57 -07:00
|
|
|
} else if (tNode.type === TNodeType.Element) {
|
2019-01-10 13:34:39 -08:00
|
|
|
if (ngDevMode) {
|
|
|
|
validateProperty(propName);
|
|
|
|
ngDevMode.rendererSetProperty++;
|
|
|
|
}
|
2019-01-24 08:53:00 -08:00
|
|
|
|
|
|
|
savePropertyDebugData(tNode, lView, propName, lView[TVIEW].data, nativeOnly);
|
|
|
|
|
2019-01-03 18:24:21 -08:00
|
|
|
const renderer = loadRendererFn ? loadRendererFn(tNode, lView) : lView[RENDERER];
|
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.
|
2019-01-03 10:04:06 -08:00
|
|
|
value = sanitizer != null ? (sanitizer(value, tNode.tagName || '', propName) as any) : value;
|
2018-12-05 15:56:36 -08:00
|
|
|
if (isProceduralRenderer(renderer)) {
|
|
|
|
renderer.setProperty(element as RElement, propName, value);
|
|
|
|
} else if (!isAnimationProp(propName)) {
|
|
|
|
(element as RElement).setProperty ? (element as any).setProperty(propName, value) :
|
|
|
|
(element as any)[propName] = value;
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-24 08:53:00 -08:00
|
|
|
/**
|
|
|
|
* Stores debugging data for this property binding on first template pass.
|
|
|
|
* This enables features like DebugElement.properties.
|
|
|
|
*/
|
|
|
|
function savePropertyDebugData(
|
|
|
|
tNode: TNode, lView: LView, propName: string, tData: TData,
|
|
|
|
nativeOnly: boolean | undefined): void {
|
|
|
|
const lastBindingIndex = lView[BINDING_INDEX] - 1;
|
|
|
|
|
|
|
|
// Bind/interpolation functions save binding metadata in the last binding index,
|
|
|
|
// but leave the property name blank. If the interpolation delimiter is at the 0
|
|
|
|
// index, we know that this is our first pass and the property name still needs to
|
|
|
|
// be set.
|
|
|
|
const bindingMetadata = tData[lastBindingIndex] as string;
|
|
|
|
if (bindingMetadata[0] == INTERPOLATION_DELIMITER) {
|
|
|
|
tData[lastBindingIndex] = propName + bindingMetadata;
|
|
|
|
|
|
|
|
// We don't want to store indices for host bindings because they are stored in a
|
|
|
|
// different part of LView (the expando section).
|
|
|
|
if (!nativeOnly) {
|
|
|
|
if (tNode.propertyMetadataStartIndex == -1) {
|
|
|
|
tNode.propertyMetadataStartIndex = lastBindingIndex;
|
|
|
|
}
|
|
|
|
tNode.propertyMetadataEndIndex = lastBindingIndex + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-06-07 22:42:32 -07:00
|
|
|
* @param adjustedIndex The index of the TNode in TView.data, adjusted for HEADER_OFFSET
|
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-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(
|
2019-01-24 23:50:12 +00:00
|
|
|
tParent: TElementNode | TContainerNode | null, type: TNodeType, adjustedIndex: number,
|
|
|
|
tagName: string | null, attrs: TAttributes | 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-06-07 22:42:32 -07:00
|
|
|
index: adjustedIndex,
|
2018-10-12 18:49:00 -07:00
|
|
|
injectorIndex: tParent ? tParent.injectorIndex : -1,
|
2018-11-28 15:54:38 -08:00
|
|
|
directiveStart: -1,
|
|
|
|
directiveEnd: -1,
|
2019-01-24 08:53:00 -08:00
|
|
|
propertyMetadataStartIndex: -1,
|
|
|
|
propertyMetadataEndIndex: -1,
|
2018-03-20 19:06:49 -07:00
|
|
|
flags: 0,
|
2018-10-18 09:23:18 +02:00
|
|
|
providerIndexes: 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,
|
2019-01-24 23:50:12 +00:00
|
|
|
tViews: null,
|
2018-05-16 05:56:01 -07:00
|
|
|
next: null,
|
2018-05-24 13:13:51 -07:00
|
|
|
child: null,
|
2018-10-12 18:49:00 -07:00
|
|
|
parent: tParent,
|
2018-06-19 12:45:00 -07:00
|
|
|
detached: null,
|
2018-07-03 20:04:36 -07:00
|
|
|
stylingTemplate: null,
|
|
|
|
projection: null
|
2017-12-08 11:48:54 -08:00
|
|
|
};
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-12-20 17:23:25 -08:00
|
|
|
* Set the inputs of directives at the current node to corresponding value.
|
|
|
|
*
|
|
|
|
* @param lView the `LView` which contains the directives.
|
|
|
|
* @param inputAliases mapping between the public "input" name and privately-known,
|
|
|
|
* possibly minified, property names to write to.
|
|
|
|
* @param value Value to set.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2019-01-14 17:39:21 -08:00
|
|
|
function setInputsForProperty(lView: LView, inputs: PropertyAliasValue, value: any): void {
|
2019-01-16 09:35:35 -08:00
|
|
|
const tView = lView[TVIEW];
|
|
|
|
for (let i = 0; i < inputs.length;) {
|
|
|
|
const index = inputs[i++] as number;
|
|
|
|
const publicName = inputs[i++] as string;
|
|
|
|
const privateName = inputs[i++] as string;
|
|
|
|
const instance = lView[index];
|
|
|
|
ngDevMode && assertDataInRange(lView, index);
|
|
|
|
const def = tView.data[index] as DirectiveDef<any>;
|
|
|
|
const setInput = def.setInput;
|
|
|
|
if (setInput) {
|
|
|
|
def.setInput !(instance, value, publicName, privateName);
|
|
|
|
} else {
|
|
|
|
instance[privateName] = value;
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:39:43 +01:00
|
|
|
function setNgReflectProperties(
|
2018-11-30 17:45:04 +01:00
|
|
|
lView: LView, element: RElement | RComment, type: TNodeType, inputs: PropertyAliasValue,
|
|
|
|
value: any) {
|
2019-01-16 09:35:35 -08:00
|
|
|
for (let i = 0; i < inputs.length; i += 3) {
|
2018-11-30 17:45:04 +01:00
|
|
|
const renderer = lView[RENDERER];
|
2019-01-16 09:35:35 -08:00
|
|
|
const attrName = normalizeDebugBindingName(inputs[i + 2] as string);
|
2018-11-30 17:45:04 +01:00
|
|
|
const debugValue = normalizeDebugBindingValue(value);
|
|
|
|
if (type === TNodeType.Element) {
|
|
|
|
isProceduralRenderer(renderer) ?
|
|
|
|
renderer.setAttribute((element as RElement), attrName, debugValue) :
|
|
|
|
(element as RElement).setAttribute(attrName, debugValue);
|
|
|
|
} else if (value !== undefined) {
|
|
|
|
const value = `bindings=${JSON.stringify({[attrName]: debugValue}, null, 2)}`;
|
|
|
|
if (isProceduralRenderer(renderer)) {
|
|
|
|
renderer.setValue((element as RComment), value);
|
|
|
|
} else {
|
|
|
|
(element as RComment).textContent = value;
|
|
|
|
}
|
2018-11-29 16:39:43 +01:00
|
|
|
}
|
|
|
|
}
|
2018-11-21 13:43:00 +01:00
|
|
|
}
|
|
|
|
|
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-11-30 17:45:04 +01:00
|
|
|
* @param tNodeFlags node flags
|
|
|
|
* @param direction whether to consider inputs or outputs
|
2018-02-07 22:19:24 -08:00
|
|
|
* @returns PropertyAliases|null aggregate of all properties if any, `null` otherwise
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-11-28 15:54:38 -08:00
|
|
|
function generatePropertyAliases(tNode: TNode, direction: BindingDirection): PropertyAliases|null {
|
2018-11-21 21:14:06 -08:00
|
|
|
const tView = getLView()[TVIEW];
|
2018-02-07 22:19:24 -08:00
|
|
|
let propStore: PropertyAliases|null = null;
|
2018-11-28 15:54:38 -08:00
|
|
|
const start = tNode.directiveStart;
|
|
|
|
const end = tNode.directiveEnd;
|
2018-02-07 22:19:24 -08:00
|
|
|
|
2018-11-28 15:54:38 -08:00
|
|
|
if (end > start) {
|
2018-02-07 22:19:24 -08:00
|
|
|
const isInput = direction === BindingDirection.Input;
|
2018-10-08 16:04:46 -07:00
|
|
|
const defs = tView.data;
|
2018-02-07 22:19:24 -08:00
|
|
|
|
2018-04-12 14:52:00 -07:00
|
|
|
for (let i = start; i < end; i++) {
|
2018-09-21 12:12:06 -07:00
|
|
|
const directiveDef = defs[i] as DirectiveDef<any>;
|
2019-01-14 17:39:21 -08:00
|
|
|
const propertyAliasMap: {[publicName: string]: string} =
|
2018-02-07 22:19:24 -08:00
|
|
|
isInput ? directiveDef.inputs : directiveDef.outputs;
|
2019-01-14 17:39:21 -08:00
|
|
|
for (let publicName in propertyAliasMap) {
|
|
|
|
if (propertyAliasMap.hasOwnProperty(publicName)) {
|
2018-02-07 22:19:24 -08:00
|
|
|
propStore = propStore || {};
|
2019-01-14 17:39:21 -08:00
|
|
|
const internalName = propertyAliasMap[publicName];
|
|
|
|
const hasProperty = propStore.hasOwnProperty(publicName);
|
2019-01-16 09:35:35 -08:00
|
|
|
hasProperty ? propStore[publicName].push(i, publicName, internalName) :
|
|
|
|
(propStore[publicName] = [i, publicName, internalName]);
|
2018-02-07 22:19:24 -08:00
|
|
|
}
|
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-12-13 15:51:47 -08:00
|
|
|
* Assign any inline style values to the element during creation mode.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-12-13 15:51:47 -08:00
|
|
|
* This instruction is meant to be called during creation mode to register all
|
|
|
|
* dynamic style and class bindings on the element. Note for static values (no binding)
|
|
|
|
* see `elementStart` and `elementHostAttrs`.
|
|
|
|
*
|
|
|
|
* @param classBindingNames An array containing bindable class names.
|
|
|
|
* The `elementClassProp` refers to the class name by index in this array.
|
|
|
|
* (i.e. `['foo', 'bar']` means `foo=0` and `bar=1`).
|
|
|
|
* @param styleBindingNames An array containing bindable style properties.
|
|
|
|
* The `elementStyleProp` refers to the class name by index in this array.
|
|
|
|
* (i.e. `['width', 'height']` means `width=0` and `height=1`).
|
|
|
|
* @param styleSanitizer An optional sanitizer function that will be used to sanitize any CSS
|
|
|
|
* property values that are applied to the element (during rendering).
|
|
|
|
* Note that the sanitizer instance itself is tied to the `directive` (if provided).
|
|
|
|
* @param directive A directive instance the styling is associated with. If not provided
|
|
|
|
* current view's controller instance is assumed.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-12-13 15:51:47 -08:00
|
|
|
* @publicApi
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-12-13 15:51:47 -08:00
|
|
|
export function elementStyling(
|
|
|
|
classBindingNames?: string[] | null, styleBindingNames?: string[] | null,
|
|
|
|
styleSanitizer?: StyleSanitizeFn | null, directive?: {}): void {
|
|
|
|
const tNode = getPreviousOrParentTNode();
|
|
|
|
if (!tNode.stylingTemplate) {
|
|
|
|
tNode.stylingTemplate = createEmptyStylingContext();
|
2018-11-19 14:55:57 -08:00
|
|
|
}
|
2018-12-13 15:51:47 -08:00
|
|
|
updateContextWithBindings(
|
|
|
|
tNode.stylingTemplate !, directive || null, classBindingNames, styleBindingNames,
|
|
|
|
styleSanitizer, hasClassInput(tNode));
|
2018-03-08 13:57:56 -08:00
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
2019-01-11 14:03:37 -08:00
|
|
|
* Assign static attribute values to a host element.
|
|
|
|
*
|
|
|
|
* This instruction will assign static attribute values as well as class and style
|
|
|
|
* values to an element within the host bindings function. Since attribute values
|
|
|
|
* can consist of different types of values, the `attrs` array must include the values in
|
|
|
|
* the following format:
|
|
|
|
*
|
|
|
|
* attrs = [
|
|
|
|
* // static attributes (like `title`, `name`, `id`...)
|
|
|
|
* attr1, value1, attr2, value,
|
|
|
|
*
|
|
|
|
* // a single namespace value (like `x:id`)
|
|
|
|
* NAMESPACE_MARKER, namespaceUri1, name1, value1,
|
|
|
|
*
|
|
|
|
* // another single namespace value (like `x:name`)
|
|
|
|
* NAMESPACE_MARKER, namespaceUri2, name2, value2,
|
|
|
|
*
|
|
|
|
* // a series of CSS classes that will be applied to the element (no spaces)
|
|
|
|
* CLASSES_MARKER, class1, class2, class3,
|
|
|
|
*
|
|
|
|
* // a series of CSS styles (property + value) that will be applied to the element
|
|
|
|
* STYLES_MARKER, prop1, value1, prop2, value2
|
|
|
|
* ]
|
|
|
|
*
|
|
|
|
* All non-class and non-style attributes must be defined at the start of the list
|
|
|
|
* first before all class and style values are set. When there is a change in value
|
|
|
|
* type (like when classes and styles are introduced) a marker must be used to separate
|
|
|
|
* the entries. The marker values themselves are set via entries found in the
|
|
|
|
* [AttributeMarker] enum.
|
2018-06-19 12:45:00 -07:00
|
|
|
*
|
2018-12-13 15:51:47 -08:00
|
|
|
* NOTE: This instruction is meant to used from `hostBindings` function only.
|
|
|
|
*
|
|
|
|
* @param directive A directive instance the styling is associated with.
|
2019-01-11 14:03:37 -08:00
|
|
|
* @param attrs An array of static values (attributes, classes and styles) with the correct marker
|
|
|
|
* values.
|
2018-12-13 15:51:47 -08:00
|
|
|
*
|
|
|
|
* @publicApi
|
2018-07-11 09:56:47 -07:00
|
|
|
*/
|
2018-12-13 15:51:47 -08:00
|
|
|
export function elementHostAttrs(directive: any, attrs: TAttributes) {
|
2018-10-18 09:23:18 +02:00
|
|
|
const tNode = getPreviousOrParentTNode();
|
2018-06-19 12:45:00 -07:00
|
|
|
if (!tNode.stylingTemplate) {
|
2018-12-13 15:51:47 -08:00
|
|
|
tNode.stylingTemplate = initializeStaticStylingContext(attrs);
|
2018-06-19 12:45:00 -07:00
|
|
|
}
|
2019-01-11 14:03:37 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const native = getNativeByTNode(tNode, lView) as RElement;
|
|
|
|
const i = setUpAttributes(native, attrs);
|
|
|
|
patchContextWithStaticAttrs(tNode.stylingTemplate, attrs, i, directive);
|
2018-06-19 12:45:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-12-13 15:51:47 -08:00
|
|
|
* Apply styling binding to the element.
|
2018-06-19 12:45:00 -07:00
|
|
|
*
|
2018-12-13 15:51:47 -08:00
|
|
|
* This instruction is meant to be run after `elementStyle` and/or `elementStyleProp`.
|
|
|
|
* if any styling bindings have changed then the changes are flushed to the element.
|
2018-06-19 12:45:00 -07:00
|
|
|
*
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-12-13 15:51:47 -08:00
|
|
|
* @param index Index of the element's with which styling is associated.
|
|
|
|
* @param directive Directive instance that is attempting to change styling. (Defaults to the
|
|
|
|
* component of the current view).
|
|
|
|
components
|
|
|
|
*
|
|
|
|
* @publicApi
|
2018-06-19 12:45:00 -07:00
|
|
|
*/
|
2018-12-13 15:51:47 -08:00
|
|
|
export function elementStylingApply(index: number, directive?: any): void {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
2018-12-18 16:58:51 -08:00
|
|
|
const isFirstRender = (lView[FLAGS] & LViewFlags.FirstLViewPass) !== 0;
|
2018-12-13 15:51:47 -08:00
|
|
|
const totalPlayersQueued = renderStyling(
|
|
|
|
getStylingContext(index + HEADER_OFFSET, lView), lView[RENDERER], lView, isFirstRender, null,
|
|
|
|
null, directive);
|
2018-10-03 14:09:59 -07:00
|
|
|
if (totalPlayersQueued > 0) {
|
2018-11-21 21:14:06 -08:00
|
|
|
const rootContext = getRootContext(lView);
|
2018-10-03 14:09:59 -07:00
|
|
|
scheduleTick(rootContext, RootContextFlags.FlushPlayers);
|
|
|
|
}
|
2018-06-19 12:45:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-12-13 15:51:47 -08:00
|
|
|
* Update a style bindings value on an element.
|
2018-06-19 12:45:00 -07:00
|
|
|
*
|
|
|
|
* If the style value is `null` then it will be removed from the element
|
|
|
|
* (or assigned a different value depending if there are any styles placed
|
|
|
|
* on the element with `elementStyle` or any styles that are present
|
|
|
|
* from when the element was created (with `elementStyling`).
|
|
|
|
*
|
2018-12-13 15:51:47 -08:00
|
|
|
* (Note that the styling element is updated as part of `elementStylingApply`.)
|
2018-06-19 12:45:00 -07:00
|
|
|
*
|
2018-12-13 15:51:47 -08:00
|
|
|
* @param index Index of the element's with which styling is associated.
|
|
|
|
* @param styleIndex Index of style to update. This index value refers to the
|
|
|
|
* index of the style in the style bindings array that was passed into
|
|
|
|
* `elementStlyingBindings`.
|
|
|
|
* @param value New value to write (null to remove). Note that if a directive also
|
|
|
|
* attempts to write to the same binding value then it will only be able to
|
|
|
|
* do so if the template binding value is `null` (or doesn't exist at all).
|
2018-03-01 17:14:01 -08:00
|
|
|
* @param suffix Optional suffix. Used with scalar values to add unit such as `px`.
|
2018-07-11 10:58:18 -07:00
|
|
|
* Note that when a suffix is provided then the underlying sanitizer will
|
|
|
|
* be ignored.
|
2018-12-13 15:51:47 -08:00
|
|
|
* @param directive Directive instance that is attempting to change styling. (Defaults to the
|
|
|
|
* component of the current view).
|
|
|
|
components
|
|
|
|
*
|
|
|
|
* @publicApi
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-10-03 14:09:59 -07:00
|
|
|
export function elementStyleProp(
|
|
|
|
index: number, styleIndex: number, value: string | number | String | PlayerFactory | null,
|
2018-12-13 15:51:47 -08:00
|
|
|
suffix?: string | null, directive?: {}): void {
|
2018-06-19 12:45:00 -07:00
|
|
|
let valueToAdd: string|null = null;
|
2018-11-26 14:16:18 +01:00
|
|
|
if (value !== null) {
|
2018-07-11 10:58:18 -07:00
|
|
|
if (suffix) {
|
|
|
|
// when a suffix is applied then it will bypass
|
|
|
|
// sanitization entirely (b/c a new string is created)
|
2019-01-12 00:59:48 -08:00
|
|
|
valueToAdd = renderStringify(value) + suffix;
|
2018-07-11 10:58:18 -07:00
|
|
|
} else {
|
|
|
|
// sanitization happens by dealing with a String value
|
|
|
|
// this means that the string value will be passed through
|
|
|
|
// into the style rendering later (which is where the value
|
|
|
|
// will be sanitized before it is applied)
|
|
|
|
valueToAdd = value as any as string;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
2018-12-13 15:51:47 -08:00
|
|
|
updateElementStyleProp(
|
|
|
|
getStylingContext(index + HEADER_OFFSET, getLView()), styleIndex, valueToAdd, directive);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add or remove a class via a class binding on a DOM element.
|
|
|
|
*
|
|
|
|
* This instruction is meant to handle the [class.foo]="exp" case and, therefore,
|
|
|
|
* the class itself must already be applied using `elementStyling` within
|
|
|
|
* the creation block.
|
|
|
|
*
|
|
|
|
* @param index Index of the element's with which styling is associated.
|
|
|
|
* @param classIndex Index of class to toggle. This index value refers to the
|
|
|
|
* index of the class in the class bindings array that was passed into
|
|
|
|
* `elementStlyingBindings` (which is meant to be called before this
|
|
|
|
* function is).
|
|
|
|
* @param value A true/false value which will turn the class on or off.
|
|
|
|
* @param directive Directive instance that is attempting to change styling. (Defaults to the
|
|
|
|
* component of the current view).
|
|
|
|
components
|
|
|
|
*
|
|
|
|
* @publicApi
|
|
|
|
*/
|
|
|
|
export function elementClassProp(
|
|
|
|
index: number, classIndex: number, value: boolean | PlayerFactory, directive?: {}): void {
|
|
|
|
const onOrOffClassValue =
|
|
|
|
(value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory<boolean>) : (!!value);
|
|
|
|
updateElementClassProp(
|
|
|
|
getStylingContext(index + HEADER_OFFSET, getLView()), classIndex, onOrOffClassValue,
|
|
|
|
directive);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-03-08 13:57:56 -08:00
|
|
|
/**
|
2018-12-13 15:51:47 -08:00
|
|
|
* Update style and/or class bindings using object literal.
|
2018-03-08 13:57:56 -08:00
|
|
|
*
|
2018-12-13 15:51:47 -08:00
|
|
|
* This instruction is meant apply styling via the `[style]="exp"` and `[class]="exp"` template
|
|
|
|
* bindings. When styles are applied to the Element they will then be placed with respect to
|
|
|
|
* any styles set with `elementStyleProp`. If any styles are set to `null` then they will be
|
|
|
|
* removed from the element.
|
2018-03-08 13:57:56 -08:00
|
|
|
*
|
2018-06-19 12:45:00 -07:00
|
|
|
* (Note that the styling instruction will not be applied until `elementStylingApply` is called.)
|
2018-03-08 13:57:56 -08:00
|
|
|
*
|
2018-12-13 15:51:47 -08:00
|
|
|
* @param index Index of the element's with which styling is associated.
|
2018-07-11 09:56:47 -07:00
|
|
|
* @param classes A key/value style map of CSS classes that will be added to the given element.
|
|
|
|
* Any missing classes (that have already been applied to the element beforehand) will be
|
|
|
|
* removed (unset) from the element's list of CSS classes.
|
2018-07-11 10:58:18 -07:00
|
|
|
* @param styles A key/value style map of the styles that will be applied to the given element.
|
|
|
|
* Any missing styles (that have already been applied to the element beforehand) will be
|
|
|
|
* removed (unset) from the element's styling.
|
2018-12-13 15:51:47 -08:00
|
|
|
* @param directive Directive instance that is attempting to change styling. (Defaults to the
|
|
|
|
* component of the current view).
|
|
|
|
*
|
|
|
|
* @publicApi
|
2018-03-08 13:57:56 -08:00
|
|
|
*/
|
2018-07-11 09:56:47 -07:00
|
|
|
export function elementStylingMap<T>(
|
2018-10-18 14:47:53 -07:00
|
|
|
index: number, classes: {[key: string]: any} | string | NO_CHANGE | null,
|
2018-11-20 15:20:19 -08:00
|
|
|
styles?: {[styleName: string]: any} | NO_CHANGE | null, directive?: {}): void {
|
|
|
|
if (directive != undefined)
|
2018-11-19 14:55:57 -08:00
|
|
|
return hackImplementationOfElementStylingMap(
|
2018-11-20 15:20:19 -08:00
|
|
|
index, classes, styles, directive); // supported in next PR
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const tNode = getTNode(index, lView);
|
2018-11-28 15:54:38 -08:00
|
|
|
const stylingContext = getStylingContext(index + HEADER_OFFSET, lView);
|
2018-12-13 15:51:47 -08:00
|
|
|
if (hasClassInput(tNode) && classes !== NO_CHANGE) {
|
|
|
|
const initialClasses = getInitialClassNameValue(stylingContext);
|
2018-10-18 14:47:53 -07:00
|
|
|
const classInputVal =
|
|
|
|
(initialClasses.length ? (initialClasses + ' ') : '') + (classes as string);
|
2019-01-14 17:39:21 -08:00
|
|
|
setInputsForProperty(lView, tNode.inputs !['class'] !, classInputVal);
|
2018-11-19 14:55:57 -08:00
|
|
|
} else {
|
2018-12-13 15:51:47 -08:00
|
|
|
updateStylingMap(stylingContext, classes, styles);
|
2018-11-19 14:55:57 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-13 15:51:47 -08:00
|
|
|
/* START OF HACK BLOCK */
|
2018-11-19 14:55:57 -08:00
|
|
|
function hackImplementationOfElementStylingMap<T>(
|
|
|
|
index: number, classes: {[key: string]: any} | string | NO_CHANGE | null,
|
2018-11-20 15:20:19 -08:00
|
|
|
styles?: {[styleName: string]: any} | NO_CHANGE | null, directive?: {}): void {
|
2018-11-19 14:55:57 -08:00
|
|
|
throw new Error('unimplemented. Should not be needed by ViewEngine compatibility');
|
|
|
|
}
|
|
|
|
/* END OF HACK BLOCK */
|
2018-12-13 15:51:47 -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
|
|
|
|
*
|
2018-06-07 22:42:32 -07: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-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
2018-08-16 18:53:21 -07:00
|
|
|
ngDevMode && assertEqual(
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[BINDING_INDEX], lView[TVIEW].bindingStartIndex,
|
2018-08-16 18:53:21 -07:00
|
|
|
'text nodes should be created before any bindings');
|
2018-04-14 11:52:53 -07:00
|
|
|
ngDevMode && ngDevMode.rendererCreateTextNode++;
|
2018-11-21 21:14:06 -08:00
|
|
|
const textNative = createTextNode(value, lView[RENDERER]);
|
2018-09-13 16:07:23 -07:00
|
|
|
const tNode = createNodeAtIndex(index, TNodeType.Element, textNative, null, null);
|
2018-05-11 20:57:37 -07:00
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
// Text nodes are self closing.
|
2018-10-18 09:23:18 +02:00
|
|
|
setIsParent(false);
|
2018-11-21 21:14:06 -08:00
|
|
|
appendChild(textNative, tNode, lView);
|
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) {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
|
|
|
|
const element = getNativeByIndex(index, lView) as any as RText;
|
2018-10-12 18:49:00 -07:00
|
|
|
ngDevMode && assertDefined(element, 'native element should exist');
|
2018-05-15 22:07:20 +02:00
|
|
|
ngDevMode && ngDevMode.rendererSetText++;
|
2018-11-21 21:14:06 -08:00
|
|
|
const renderer = lView[RENDERER];
|
2019-01-12 00:59:48 -08:00
|
|
|
isProceduralRenderer(renderer) ? renderer.setValue(element, renderStringify(value)) :
|
|
|
|
element.textContent = renderStringify(value);
|
2018-05-15 22:07:20 +02:00
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////
|
|
|
|
//// Directive
|
|
|
|
//////////////////////////
|
|
|
|
|
2018-01-08 21:57:50 -08:00
|
|
|
/**
|
2018-10-18 09:23:18 +02:00
|
|
|
* Instantiate a root component.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-10-18 09:23:18 +02:00
|
|
|
export function instantiateRootComponent<T>(
|
2018-11-21 21:14:06 -08:00
|
|
|
tView: TView, viewData: LView, def: ComponentDef<T>): T {
|
2018-10-25 19:10:32 -07:00
|
|
|
const rootTNode = getPreviousOrParentTNode();
|
|
|
|
if (tView.firstTemplatePass) {
|
2018-10-18 09:23:18 +02:00
|
|
|
if (def.providersResolver) def.providersResolver(def);
|
2018-10-25 19:10:32 -07:00
|
|
|
generateExpandoInstructionBlock(tView, rootTNode, 1);
|
2018-10-18 09:23:18 +02:00
|
|
|
baseResolveDirective(tView, viewData, def, def.factory);
|
2018-03-25 21:32:39 -07:00
|
|
|
}
|
2018-10-25 19:10:32 -07:00
|
|
|
const directive =
|
|
|
|
getNodeInjectable(tView.data, viewData, viewData.length - 1, rootTNode as TElementNode);
|
|
|
|
postProcessBaseDirective(viewData, rootTNode, directive, def as DirectiveDef<T>);
|
2018-10-18 09:23:18 +02:00
|
|
|
return directive;
|
|
|
|
}
|
2018-03-25 21:32:39 -07:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
/**
|
|
|
|
* Resolve the matched directives on a node.
|
|
|
|
*/
|
|
|
|
function resolveDirectives(
|
2018-11-21 21:14:06 -08:00
|
|
|
tView: TView, viewData: LView, directives: DirectiveDef<any>[] | null, tNode: TNode,
|
2018-10-18 09:23:18 +02:00
|
|
|
localRefs: string[] | null): void {
|
|
|
|
// Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in tsickle.
|
2019-01-30 16:38:59 +01:00
|
|
|
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'should run on first template pass only');
|
2018-10-18 09:23:18 +02:00
|
|
|
const exportsMap: ({[key: string]: number} | null) = localRefs ? {'': -1} : null;
|
|
|
|
if (directives) {
|
|
|
|
initNodeFlags(tNode, tView.data.length, directives.length);
|
|
|
|
// When the same token is provided by several directives on the same node, some rules apply in
|
|
|
|
// the viewEngine:
|
|
|
|
// - viewProviders have priority over providers
|
|
|
|
// - the last directive in NgModule.declarations has priority over the previous one
|
|
|
|
// So to match these rules, the order in which providers are added in the arrays is very
|
|
|
|
// important.
|
|
|
|
for (let i = 0; i < directives.length; i++) {
|
|
|
|
const def = directives[i] as DirectiveDef<any>;
|
|
|
|
if (def.providersResolver) def.providersResolver(def);
|
|
|
|
}
|
2018-10-25 19:10:32 -07:00
|
|
|
generateExpandoInstructionBlock(tView, tNode, directives.length);
|
2018-10-18 09:23:18 +02:00
|
|
|
for (let i = 0; i < directives.length; i++) {
|
|
|
|
const def = directives[i] as DirectiveDef<any>;
|
2018-03-16 20:31:24 -07:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
const directiveDefIdx = tView.data.length;
|
|
|
|
baseResolveDirective(tView, viewData, def, def.factory);
|
2018-07-10 10:43:07 +02:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
saveNameToExportMap(tView.data !.length - 1, def, exportsMap);
|
2018-03-16 20:31:24 -07:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
// Init hooks are queued now so ngOnInit is called in host components before
|
|
|
|
// any projected components.
|
2018-12-20 17:23:25 -08:00
|
|
|
registerPreOrderHooks(directiveDefIdx, def, tView);
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
|
2018-03-16 20:31:24 -07:00
|
|
|
}
|
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
/**
|
|
|
|
* Instantiate all the directives that were previously resolved on the current node.
|
|
|
|
*/
|
2018-11-28 15:54:38 -08:00
|
|
|
function instantiateAllDirectives(tView: TView, lView: LView, tNode: TNode) {
|
|
|
|
const start = tNode.directiveStart;
|
|
|
|
const end = tNode.directiveEnd;
|
2019-01-30 16:38:59 +01:00
|
|
|
if (!tView.firstTemplatePass && start < end) {
|
2018-10-18 09:23:18 +02:00
|
|
|
getOrCreateNodeInjectorForNode(
|
2018-11-28 15:54:38 -08:00
|
|
|
tNode as TElementNode | TContainerNode | TElementContainerNode, lView);
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
|
|
|
for (let i = start; i < end; i++) {
|
|
|
|
const def = tView.data[i] as DirectiveDef<any>;
|
|
|
|
if (isComponentDef(def)) {
|
2018-11-28 15:54:38 -08:00
|
|
|
addComponentLogic(lView, tNode, def as ComponentDef<any>);
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
2018-11-28 15:54:38 -08:00
|
|
|
const directive = getNodeInjectable(tView.data, lView !, i, tNode as TElementNode);
|
|
|
|
postProcessDirective(lView, directive, def, i);
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
|
|
|
}
|
2018-09-20 11:48:06 -07:00
|
|
|
|
2018-11-28 15:54:38 -08:00
|
|
|
function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNode) {
|
|
|
|
const start = tNode.directiveStart;
|
|
|
|
const end = tNode.directiveEnd;
|
2018-11-27 12:05:26 -08:00
|
|
|
const expando = tView.expandoInstructions !;
|
2019-01-30 16:38:59 +01:00
|
|
|
const firstTemplatePass = tView.firstTemplatePass;
|
2018-11-27 12:05:26 -08:00
|
|
|
for (let i = start; i < end; i++) {
|
|
|
|
const def = tView.data[i] as DirectiveDef<any>;
|
2019-01-14 17:39:21 -08:00
|
|
|
const directive = viewData[i];
|
2018-11-27 12:05:26 -08:00
|
|
|
if (def.hostBindings) {
|
|
|
|
const previousExpandoLength = expando.length;
|
|
|
|
setCurrentDirectiveDef(def);
|
2018-12-15 15:57:57 -08:00
|
|
|
def.hostBindings !(RenderFlags.Create, directive, tNode.index - HEADER_OFFSET);
|
2018-11-27 12:05:26 -08:00
|
|
|
setCurrentDirectiveDef(null);
|
|
|
|
// `hostBindings` function may or may not contain `allocHostVars` call
|
|
|
|
// (e.g. it may not if it only contains host listeners), so we need to check whether
|
2018-11-30 17:34:36 -08:00
|
|
|
// `expandoInstructions` has changed and if not - we still push `hostBindings` to
|
|
|
|
// expando block, to make sure we execute it for DI cycle
|
2018-11-27 12:05:26 -08:00
|
|
|
if (previousExpandoLength === expando.length && firstTemplatePass) {
|
2018-11-30 17:34:36 -08:00
|
|
|
expando.push(def.hostBindings);
|
2018-11-27 12:05:26 -08:00
|
|
|
}
|
|
|
|
} else if (firstTemplatePass) {
|
|
|
|
expando.push(null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
/**
|
|
|
|
* Generates a new block in TView.expandoInstructions for this node.
|
|
|
|
*
|
|
|
|
* Each expando block starts with the element index (turned negative so we can distinguish
|
|
|
|
* it from the hostVar count) and the directive count. See more in VIEW_DATA.md.
|
|
|
|
*/
|
2018-10-25 19:10:32 -07:00
|
|
|
export function generateExpandoInstructionBlock(
|
|
|
|
tView: TView, tNode: TNode, directiveCount: number): void {
|
|
|
|
ngDevMode && assertEqual(
|
|
|
|
tView.firstTemplatePass, true,
|
|
|
|
'Expando block should only be generated on first template pass.');
|
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
const elementIndex = -(tNode.index - HEADER_OFFSET);
|
2018-10-25 19:10:32 -07:00
|
|
|
const providerStartIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
|
|
|
|
const providerCount = tView.data.length - providerStartIndex;
|
|
|
|
(tView.expandoInstructions || (tView.expandoInstructions = [
|
|
|
|
])).push(elementIndex, providerCount, directiveCount);
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
2018-03-25 21:32:39 -07:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
/**
|
|
|
|
* On the first template pass, we need to reserve space for host binding values
|
|
|
|
* after directives are matched (so all directives are saved, then bindings).
|
|
|
|
* Because we are updating the blueprint, we only need to do this once.
|
|
|
|
*/
|
2018-11-27 12:05:26 -08:00
|
|
|
function prefillHostVars(tView: TView, lView: LView, totalHostVars: number): void {
|
|
|
|
ngDevMode &&
|
2019-01-30 16:38:59 +01:00
|
|
|
assertEqual(tView.firstTemplatePass, true, 'Should only be called in first template pass.');
|
2018-10-18 09:23:18 +02:00
|
|
|
for (let i = 0; i < totalHostVars; i++) {
|
2018-11-21 21:14:06 -08:00
|
|
|
lView.push(NO_CHANGE);
|
2018-10-18 09:23:18 +02:00
|
|
|
tView.blueprint.push(NO_CHANGE);
|
|
|
|
tView.data.push(null);
|
|
|
|
}
|
|
|
|
}
|
2018-03-25 21:32:39 -07:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
/**
|
|
|
|
* Process a directive on the current node after its creation.
|
|
|
|
*/
|
|
|
|
function postProcessDirective<T>(
|
2019-01-14 17:39:21 -08:00
|
|
|
viewData: LView, directive: T, def: DirectiveDef<T>, directiveDefIdx: number): void {
|
2018-10-18 09:23:18 +02:00
|
|
|
const previousOrParentTNode = getPreviousOrParentTNode();
|
2019-01-14 17:39:21 -08:00
|
|
|
postProcessBaseDirective(viewData, previousOrParentTNode, directive, def);
|
2018-10-18 09:23:18 +02:00
|
|
|
ngDevMode && assertDefined(previousOrParentTNode, 'previousOrParentTNode');
|
|
|
|
if (previousOrParentTNode && previousOrParentTNode.attrs) {
|
2019-01-16 09:35:35 -08:00
|
|
|
setInputsFromAttrs(directiveDefIdx, directive, def, previousOrParentTNode);
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
2018-10-12 15:02:54 -07:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
if (def.contentQueries) {
|
|
|
|
def.contentQueries(directiveDefIdx);
|
|
|
|
}
|
2018-03-25 21:32:39 -07:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
if (isComponentDef(def)) {
|
2019-01-14 17:39:21 -08:00
|
|
|
const componentView = getComponentViewByIndex(previousOrParentTNode.index, viewData);
|
2018-10-18 09:23:18 +02:00
|
|
|
componentView[CONTEXT] = directive;
|
2018-10-08 16:04:46 -07:00
|
|
|
}
|
2018-03-25 21:32:39 -07:00
|
|
|
}
|
|
|
|
|
2018-03-16 20:31:24 -07:00
|
|
|
/**
|
2018-10-18 09:23:18 +02:00
|
|
|
* A lighter version of postProcessDirective() that is used for the root component.
|
2018-03-16 20:31:24 -07:00
|
|
|
*/
|
2018-10-18 09:23:18 +02:00
|
|
|
function postProcessBaseDirective<T>(
|
2018-11-21 21:14:06 -08:00
|
|
|
lView: LView, previousOrParentTNode: TNode, directive: T, def: DirectiveDef<T>): void {
|
|
|
|
const native = getNativeByTNode(previousOrParentTNode, lView);
|
2018-10-18 09:23:18 +02:00
|
|
|
|
2018-08-16 18:53:21 -07:00
|
|
|
ngDevMode && assertEqual(
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[BINDING_INDEX], lView[TVIEW].bindingStartIndex,
|
2018-08-16 18:53:21 -07:00
|
|
|
'directives should be created before any bindings');
|
2018-11-21 21:14:06 -08:00
|
|
|
ngDevMode && assertPreviousIsParent(getIsParent());
|
2018-03-20 19:06:49 -07:00
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
attachPatchData(directive, lView);
|
2018-10-12 15:02:54 -07:00
|
|
|
if (native) {
|
2018-11-21 21:14:06 -08:00
|
|
|
attachPatchData(native, lView);
|
2018-09-10 11:22:35 -07:00
|
|
|
}
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
2018-03-21 15:10:34 -07:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Matches the current node against all available selectors.
|
|
|
|
* If a component is matched (at most one), it is returned in first position in the array.
|
|
|
|
*/
|
2018-11-21 21:14:06 -08:00
|
|
|
function findDirectiveMatches(tView: TView, viewData: LView, tNode: TNode): DirectiveDef<any>[]|
|
2018-10-18 09:23:18 +02:00
|
|
|
null {
|
2019-01-30 16:38:59 +01:00
|
|
|
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'should run on first template pass only');
|
2018-10-18 09:23:18 +02:00
|
|
|
const registry = tView.directiveRegistry;
|
|
|
|
let matches: any[]|null = null;
|
|
|
|
if (registry) {
|
|
|
|
for (let i = 0; i < registry.length; i++) {
|
|
|
|
const def = registry[i] as ComponentDef<any>| DirectiveDef<any>;
|
2018-12-12 15:23:12 -08:00
|
|
|
if (isNodeMatchingSelectorList(tNode, def.selectors !, /* isProjectionMode */ false)) {
|
2018-10-18 09:23:18 +02:00
|
|
|
matches || (matches = []);
|
|
|
|
diPublicInInjector(
|
|
|
|
getOrCreateNodeInjectorForNode(
|
|
|
|
getPreviousOrParentTNode() as TElementNode | TContainerNode | TElementContainerNode,
|
|
|
|
viewData),
|
|
|
|
viewData, def.type);
|
|
|
|
|
|
|
|
if (isComponentDef(def)) {
|
|
|
|
if (tNode.flags & TNodeFlags.isComponent) throwMultipleComponentError(tNode);
|
|
|
|
tNode.flags = TNodeFlags.isComponent;
|
|
|
|
|
|
|
|
// The component is always stored first with directives after.
|
|
|
|
matches.unshift(def);
|
|
|
|
} else {
|
|
|
|
matches.push(def);
|
|
|
|
}
|
|
|
|
}
|
2018-04-12 14:52:00 -07:00
|
|
|
}
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
|
|
|
return matches;
|
|
|
|
}
|
2018-10-08 16:04:46 -07:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
/** Stores index of component's host element so it will be queued for view refresh during CD. */
|
2018-10-30 22:10:23 -07:00
|
|
|
export function queueComponentIndexForCheck(previousOrParentTNode: TNode): void {
|
2018-11-21 21:14:06 -08:00
|
|
|
const tView = getLView()[TVIEW];
|
2019-01-30 16:38:59 +01:00
|
|
|
ngDevMode &&
|
|
|
|
assertEqual(tView.firstTemplatePass, true, 'Should only be called in first template pass.');
|
2018-10-18 09:23:18 +02:00
|
|
|
(tView.components || (tView.components = [])).push(previousOrParentTNode.index);
|
|
|
|
}
|
|
|
|
|
2018-11-27 12:05:26 -08:00
|
|
|
/**
|
|
|
|
* Stores host binding fn and number of host vars so it will be queued for binding refresh during
|
|
|
|
* CD.
|
2018-10-18 09:23:18 +02:00
|
|
|
*/
|
2018-11-27 12:05:26 -08:00
|
|
|
function queueHostBindingForCheck(
|
|
|
|
tView: TView, def: DirectiveDef<any>| ComponentDef<any>, hostVars: number): void {
|
2018-10-18 09:23:18 +02:00
|
|
|
ngDevMode &&
|
2019-01-30 16:38:59 +01:00
|
|
|
assertEqual(tView.firstTemplatePass, true, 'Should only be called in first template pass.');
|
2018-11-27 12:05:26 -08:00
|
|
|
const expando = tView.expandoInstructions !;
|
2018-12-01 10:33:23 -08:00
|
|
|
const length = expando.length;
|
|
|
|
// Check whether a given `hostBindings` function already exists in expandoInstructions,
|
2018-11-27 12:05:26 -08:00
|
|
|
// which can happen in case directive definition was extended from base definition (as a part of
|
2018-12-01 10:33:23 -08:00
|
|
|
// the `InheritDefinitionFeature` logic). If we found the same `hostBindings` function in the
|
|
|
|
// list, we just increase the number of host vars associated with that function, but do not add it
|
|
|
|
// into the list again.
|
|
|
|
if (length >= 2 && expando[length - 2] === def.hostBindings) {
|
|
|
|
expando[length - 1] = (expando[length - 1] as number) + hostVars;
|
|
|
|
} else {
|
2018-11-27 12:05:26 -08:00
|
|
|
expando.push(def.hostBindings !, hostVars);
|
|
|
|
}
|
2018-10-18 09:23:18 +02: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) {
|
|
|
|
const index = exportsMap[localRefs[i + 1]];
|
|
|
|
if (index == null) throw new Error(`Export of name '${localRefs[i + 1]}' not found!`);
|
|
|
|
localNames.push(localRefs[i], index);
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
2017-12-11 14:08:52 -08:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
/**
|
|
|
|
* Builds up an export map as directives are created, so local refs can be quickly mapped
|
|
|
|
* to their directive instances.
|
|
|
|
*/
|
|
|
|
function saveNameToExportMap(
|
|
|
|
index: number, def: DirectiveDef<any>| ComponentDef<any>,
|
|
|
|
exportsMap: {[key: string]: number} | null) {
|
|
|
|
if (exportsMap) {
|
2019-01-10 22:24:32 +01:00
|
|
|
if (def.exportAs) {
|
|
|
|
for (let i = 0; i < def.exportAs.length; i++) {
|
|
|
|
exportsMap[def.exportAs[i]] = index;
|
|
|
|
}
|
|
|
|
}
|
2018-10-18 09:23:18 +02:00
|
|
|
if ((def as ComponentDef<any>).template) exportsMap[''] = index;
|
2018-02-16 12:09:47 -08:00
|
|
|
}
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
2018-02-16 12:09:47 -08:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
/**
|
|
|
|
* Initializes the flags on the current node, setting all indices to the initial index,
|
|
|
|
* the directive count to 0, and adding the isComponent flag.
|
|
|
|
* @param index the initial index
|
|
|
|
*/
|
|
|
|
export function initNodeFlags(tNode: TNode, index: number, numberOfDirectives: number) {
|
|
|
|
const flags = tNode.flags;
|
|
|
|
ngDevMode && assertEqual(
|
|
|
|
flags === 0 || flags === TNodeFlags.isComponent, true,
|
|
|
|
'expected node flags to not be initialized');
|
|
|
|
|
|
|
|
ngDevMode && assertNotEqual(
|
2018-11-28 15:54:38 -08:00
|
|
|
numberOfDirectives, tNode.directiveEnd - tNode.directiveStart,
|
2018-10-18 09:23:18 +02:00
|
|
|
'Reached the max number of directives');
|
|
|
|
// When the first directive is created on a node, save the index
|
2018-11-28 15:54:38 -08:00
|
|
|
tNode.flags = flags & TNodeFlags.isComponent;
|
|
|
|
tNode.directiveStart = index;
|
|
|
|
tNode.directiveEnd = index + numberOfDirectives;
|
2018-10-18 09:23:18 +02:00
|
|
|
tNode.providerIndexes = index;
|
|
|
|
}
|
|
|
|
|
|
|
|
function baseResolveDirective<T>(
|
2018-11-21 21:14:06 -08:00
|
|
|
tView: TView, viewData: LView, def: DirectiveDef<T>,
|
2018-10-18 09:23:18 +02:00
|
|
|
directiveFactory: (t: Type<T>| null) => any) {
|
|
|
|
tView.data.push(def);
|
2019-01-07 17:07:39 +01:00
|
|
|
const nodeInjectorFactory =
|
|
|
|
new NodeInjectorFactory(directiveFactory, isComponentDef(def), false, null);
|
2018-10-18 09:23:18 +02:00
|
|
|
tView.blueprint.push(nodeInjectorFactory);
|
|
|
|
viewData.push(nodeInjectorFactory);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addComponentLogic<T>(
|
2018-11-21 21:14:06 -08:00
|
|
|
lView: LView, previousOrParentTNode: TNode, def: ComponentDef<T>): void {
|
|
|
|
const native = getNativeByTNode(previousOrParentTNode, lView);
|
2018-10-18 09:23:18 +02:00
|
|
|
|
|
|
|
const tView = getOrCreateTView(
|
|
|
|
def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery);
|
|
|
|
|
|
|
|
// 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-11-21 21:14:06 -08:00
|
|
|
const rendererFactory = lView[RENDERER_FACTORY];
|
2018-10-18 09:23:18 +02:00
|
|
|
const componentView = addToViewTree(
|
2018-11-21 21:14:06 -08:00
|
|
|
lView, previousOrParentTNode.index as number,
|
|
|
|
createLView(
|
|
|
|
lView, tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways,
|
|
|
|
rendererFactory, lView[RENDERER_FACTORY].createRenderer(native as RElement, def)));
|
2018-10-18 09:23:18 +02:00
|
|
|
|
|
|
|
componentView[HOST_NODE] = previousOrParentTNode as TElementNode;
|
|
|
|
|
|
|
|
// Component view will always be created before any injected LContainers,
|
|
|
|
// so this is a regular element, wrap it with the component view
|
2018-11-21 21:14:06 -08:00
|
|
|
componentView[HOST] = lView[previousOrParentTNode.index];
|
|
|
|
lView[previousOrParentTNode.index] = componentView;
|
2018-10-18 09:23:18 +02:00
|
|
|
|
2019-01-30 16:38:59 +01:00
|
|
|
if (lView[TVIEW].firstTemplatePass) {
|
2018-10-18 09:23:18 +02:00
|
|
|
queueComponentIndexForCheck(previousOrParentTNode);
|
|
|
|
}
|
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>(
|
2019-01-16 09:35:35 -08:00
|
|
|
directiveIndex: number, instance: T, def: DirectiveDef<T>, 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) {
|
2019-01-16 09:35:35 -08:00
|
|
|
initialInputData = generateInitialInputs(directiveIndex, def.inputs, tNode);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
const initialInputs: InitialInputs|null = initialInputData[directiveIndex];
|
|
|
|
if (initialInputs) {
|
2019-01-16 09:35:35 -08:00
|
|
|
const setInput = def.setInput;
|
|
|
|
for (let i = 0; i < initialInputs.length;) {
|
|
|
|
const publicName = initialInputs[i++];
|
|
|
|
const privateName = initialInputs[i++];
|
|
|
|
const value = initialInputs[i++];
|
|
|
|
if (setInput) {
|
|
|
|
def.setInput !(instance, value, publicName, privateName);
|
|
|
|
} else {
|
|
|
|
(instance as any)[privateName] = value;
|
|
|
|
}
|
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(
|
2019-01-14 17:39:21 -08:00
|
|
|
directiveIndex: number, inputs: {[key: string]: string}, tNode: TNode): InitialInputData {
|
2018-01-08 20:17:13 -08:00
|
|
|
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 !;
|
2018-06-08 15:25:39 -07:00
|
|
|
let i = 0;
|
|
|
|
while (i < attrs.length) {
|
2018-06-06 13:38:20 -07:00
|
|
|
const attrName = attrs[i];
|
2018-12-20 17:23:25 -08:00
|
|
|
// If we hit Select-Only, Classes or Styles, we're done anyway. None of those are valid inputs.
|
|
|
|
if (attrName === AttributeMarker.SelectOnly || attrName === AttributeMarker.Classes ||
|
|
|
|
attrName === AttributeMarker.Styles)
|
|
|
|
break;
|
2018-06-08 15:25:39 -07:00
|
|
|
if (attrName === AttributeMarker.NamespaceURI) {
|
|
|
|
// We do not allow inputs on namespaced attributes.
|
|
|
|
i += 4;
|
|
|
|
continue;
|
|
|
|
}
|
2019-01-14 17:39:21 -08:00
|
|
|
const minifiedInputName = inputs[attrName];
|
2018-05-04 15:58:42 +02:00
|
|
|
const attrValue = attrs[i + 1];
|
|
|
|
|
2019-01-14 17:39:21 -08:00
|
|
|
if (minifiedInputName !== undefined) {
|
2017-12-01 14:23:03 -08:00
|
|
|
const inputsToStore: InitialInputs =
|
|
|
|
initialInputData[directiveIndex] || (initialInputData[directiveIndex] = []);
|
2019-01-16 09:35:35 -08:00
|
|
|
inputsToStore.push(attrName, minifiedInputName, attrValue as string);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-06-08 15:25:39 -07:00
|
|
|
|
|
|
|
i += 2;
|
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.
|
|
|
|
*
|
2018-10-12 18:49:00 -07:00
|
|
|
* @param hostNative The host element for the LContainer
|
2018-10-11 13:13:57 -07:00
|
|
|
* @param hostTNode The host TNode for the LContainer
|
2018-05-17 16:19:44 +02:00
|
|
|
* @param currentView The parent view of the LContainer
|
2018-10-11 13:13:57 -07:00
|
|
|
* @param native The native comment element
|
2018-05-17 16:19:44 +02:00
|
|
|
* @param isForViewContainerRef Optional a flag indicating the ViewContainerRef case
|
|
|
|
* @returns LContainer
|
|
|
|
*/
|
2018-03-15 17:33:35 +01:00
|
|
|
export function createLContainer(
|
2019-01-09 14:54:47 +01:00
|
|
|
hostNative: RElement | RComment, currentView: LView, native: RComment,
|
|
|
|
isForViewContainerRef?: boolean): LContainer {
|
2018-06-07 22:42:32 -07:00
|
|
|
return [
|
2019-01-09 14:54:47 +01:00
|
|
|
isForViewContainerRef ? -1 : 0, // active index
|
|
|
|
[], // views
|
|
|
|
currentView, // parent
|
|
|
|
null, // next
|
|
|
|
null, // queries
|
|
|
|
hostNative, // host native
|
|
|
|
native, // native
|
2018-06-07 22:42:32 -07:00
|
|
|
];
|
2018-03-15 17:33:35 +01:00
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
2018-10-12 18:49:00 -07:00
|
|
|
* Creates an LContainer for an ng-template (dynamically-inserted view), e.g.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-08-15 18:37:03 -07:00
|
|
|
* <ng-template #foo>
|
|
|
|
* <div></div>
|
|
|
|
* </ng-template>
|
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
|
2018-08-15 18:37:03 -07:00
|
|
|
* @param templateFn Inline template
|
2018-08-16 18:53:21 -07:00
|
|
|
* @param consts The number of nodes, local refs, and pipes for this template
|
2018-08-18 11:14:50 -07:00
|
|
|
* @param vars The number of bindings for this 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.
|
2018-08-14 16:25:01 +02:00
|
|
|
* @param localRefExtractor A function which extracts local-refs values from the template.
|
|
|
|
* Defaults to the current element associated with the local-ref.
|
2017-12-01 14:23:03 -08:00
|
|
|
*/
|
2018-08-15 18:37:03 -07:00
|
|
|
export function template(
|
2018-08-18 11:14:50 -07:00
|
|
|
index: number, templateFn: ComponentTemplate<any>| null, consts: number, vars: number,
|
2018-08-16 18:53:21 -07:00
|
|
|
tagName?: string | null, attrs?: TAttributes | null, localRefs?: string[] | null,
|
2018-08-14 16:25:01 +02:00
|
|
|
localRefExtractor?: LocalRefExtractor) {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const tView = lView[TVIEW];
|
2018-08-15 18:37:03 -07:00
|
|
|
// TODO: consider a separate node type for templates
|
2018-09-13 16:07:23 -07:00
|
|
|
const tNode = containerInternal(index, tagName || null, attrs || null);
|
2018-08-16 18:53:21 -07:00
|
|
|
|
2019-01-30 16:38:59 +01:00
|
|
|
if (tView.firstTemplatePass) {
|
2018-09-13 16:07:23 -07:00
|
|
|
tNode.tViews = createTView(
|
2018-08-18 11:14:50 -07:00
|
|
|
-1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null);
|
2018-08-15 18:37:03 -07:00
|
|
|
}
|
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
createDirectivesAndLocals(tView, lView, localRefs, localRefExtractor);
|
|
|
|
const currentQueries = lView[QUERIES];
|
2018-10-18 09:23:18 +02:00
|
|
|
const previousOrParentTNode = getPreviousOrParentTNode();
|
2018-12-17 14:19:25 +01:00
|
|
|
const native = getNativeByTNode(previousOrParentTNode, lView);
|
|
|
|
attachPatchData(native, lView);
|
2018-10-18 09:23:18 +02:00
|
|
|
if (currentQueries) {
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[QUERIES] = currentQueries.addNode(previousOrParentTNode as TContainerNode);
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
2018-12-20 17:23:25 -08:00
|
|
|
registerPostOrderHooks(tView, tNode);
|
2018-10-18 09:23:18 +02:00
|
|
|
setIsParent(false);
|
2018-08-15 18:37:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-10-12 18:49:00 -07:00
|
|
|
* Creates an LContainer for inline views, e.g.
|
2018-08-15 18:37:03 -07:00
|
|
|
*
|
|
|
|
* % if (showing) {
|
|
|
|
* <div></div>
|
|
|
|
* % }
|
|
|
|
*
|
|
|
|
* @param index The index of the container in the data array
|
|
|
|
*/
|
|
|
|
export function container(index: number): void {
|
2018-09-13 16:07:23 -07:00
|
|
|
const tNode = containerInternal(index, null, null);
|
2019-01-30 16:38:59 +01:00
|
|
|
const lView = getLView();
|
|
|
|
if (lView[TVIEW].firstTemplatePass) {
|
|
|
|
tNode.tViews = [];
|
|
|
|
}
|
2018-10-18 09:23:18 +02:00
|
|
|
setIsParent(false);
|
2018-08-15 18:37:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function containerInternal(
|
2018-09-13 16:07:23 -07:00
|
|
|
index: number, tagName: string | null, attrs: TAttributes | null): TNode {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
2018-08-16 18:53:21 -07:00
|
|
|
ngDevMode && assertEqual(
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[BINDING_INDEX], lView[TVIEW].bindingStartIndex,
|
2018-08-16 18:53:21 -07:00
|
|
|
'container nodes should be created before any bindings');
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-10-11 13:13:57 -07:00
|
|
|
const adjustedIndex = index + HEADER_OFFSET;
|
2018-11-21 21:14:06 -08:00
|
|
|
const comment = lView[RENDERER].createComment(ngDevMode ? 'container' : '');
|
2018-10-11 13:13:57 -07:00
|
|
|
ngDevMode && ngDevMode.rendererCreateComment++;
|
2018-10-12 18:49:00 -07:00
|
|
|
const tNode = createNodeAtIndex(index, TNodeType.Container, comment, tagName, attrs);
|
2019-01-09 14:54:47 +01:00
|
|
|
const lContainer = lView[adjustedIndex] = createLContainer(lView[adjustedIndex], lView, comment);
|
2018-09-13 16:07:23 -07:00
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
appendChild(comment, tNode, lView);
|
2018-05-11 20:57:37 -07:00
|
|
|
|
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-11-21 21:14:06 -08:00
|
|
|
addToViewTree(lView, index + HEADER_OFFSET, lContainer);
|
2018-06-01 17:01:24 +02:00
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
const currentQueries = lView[QUERIES];
|
2018-08-09 10:00:07 -07:00
|
|
|
if (currentQueries) {
|
2018-06-01 17:01:24 +02:00
|
|
|
// prepare place for matching nodes from views inserted into a given container
|
2018-08-09 10:00:07 -07:00
|
|
|
lContainer[QUERIES] = currentQueries.container();
|
2018-06-01 17:01:24 +02:00
|
|
|
}
|
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
ngDevMode && assertNodeType(getPreviousOrParentTNode(), TNodeType.Container);
|
2018-09-13 16:07:23 -07:00
|
|
|
return tNode;
|
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 {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const tView = lView[TVIEW];
|
|
|
|
let previousOrParentTNode = loadInternal(tView.data, index) as TNode;
|
2018-10-18 09:23:18 +02:00
|
|
|
setPreviousOrParentTNode(previousOrParentTNode);
|
2018-09-05 16:15:37 -07:00
|
|
|
|
|
|
|
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);
|
2018-10-18 09:23:18 +02:00
|
|
|
setIsParent(true);
|
2018-10-11 13:13:57 -07:00
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[index + HEADER_OFFSET][ACTIVE_INDEX] = 0;
|
2018-01-22 17:43:52 -08:00
|
|
|
|
2018-12-18 16:58:51 -08:00
|
|
|
// We need to execute init hooks here so ngOnInit hooks are called in top level views
|
|
|
|
// before they are called in embedded views (for backwards compatibility).
|
|
|
|
executeInitHooks(lView, tView, getCheckNoChangesMode());
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-10-12 18:49:00 -07:00
|
|
|
* Marks the end of the LContainer.
|
2017-12-01 14:23:03 -08:00
|
|
|
*
|
2018-10-12 18:49:00 -07:00
|
|
|
* Marking the end of LContainer 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 {
|
2018-10-18 09:23:18 +02:00
|
|
|
let previousOrParentTNode = getPreviousOrParentTNode();
|
|
|
|
if (getIsParent()) {
|
|
|
|
setIsParent(false);
|
2017-12-01 14:23:03 -08:00
|
|
|
} else {
|
2018-09-05 16:15:37 -07:00
|
|
|
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.View);
|
2018-11-21 21:14:06 -08:00
|
|
|
ngDevMode && assertHasParent(previousOrParentTNode);
|
2018-09-05 16:15:37 -07:00
|
|
|
previousOrParentTNode = previousOrParentTNode.parent !;
|
2018-10-18 09:23:18 +02:00
|
|
|
setPreviousOrParentTNode(previousOrParentTNode);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-09-05 16:15:37 -07:00
|
|
|
|
|
|
|
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);
|
2018-09-17 14:32:45 -07:00
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
const lContainer = getLView()[previousOrParentTNode.index];
|
2018-09-17 14:32:45 -07:00
|
|
|
const nextIndex = lContainer[ACTIVE_INDEX];
|
2018-03-08 12:10:20 +01:00
|
|
|
|
|
|
|
// remove extra views at the end of the container
|
2018-09-17 14:32:45 -07:00
|
|
|
while (nextIndex < lContainer[VIEWS].length) {
|
|
|
|
removeView(lContainer, previousOrParentTNode as TContainerNode, nextIndex);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-21 11:49:03 +02:00
|
|
|
/**
|
|
|
|
* Goes over dynamic embedded views (ones created through ViewContainerRef APIs) and refreshes them
|
|
|
|
* by executing an associated template function.
|
|
|
|
*/
|
2018-11-21 21:14:06 -08:00
|
|
|
function refreshDynamicEmbeddedViews(lView: LView) {
|
|
|
|
for (let current = getLViewChild(lView); current !== null; current = current[NEXT]) {
|
|
|
|
// Note: current can be an LView or an LContainer instance, but here we are only interested
|
|
|
|
// in LContainer. We can tell it's an LContainer because its length is less than the LView
|
2018-06-07 22:42:32 -07:00
|
|
|
// header.
|
2018-10-11 13:13:57 -07:00
|
|
|
if (current.length < HEADER_OFFSET && current[ACTIVE_INDEX] === -1) {
|
2018-01-17 10:09:05 -08:00
|
|
|
const container = current as LContainer;
|
2018-06-07 22:42:32 -07:00
|
|
|
for (let i = 0; i < container[VIEWS].length; i++) {
|
2018-09-12 08:47:03 -07:00
|
|
|
const dynamicViewData = 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-06-07 22:42:32 -07:00
|
|
|
ngDevMode && assertDefined(dynamicViewData[TVIEW], 'TView must be allocated');
|
2018-12-18 16:58:51 -08:00
|
|
|
renderEmbeddedTemplate(dynamicViewData, dynamicViewData[TVIEW], dynamicViewData[CONTEXT] !);
|
2018-01-17 10:09:05 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-17 16:19:44 +02:00
|
|
|
|
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.
|
|
|
|
*
|
2018-09-17 14:32:45 -07:00
|
|
|
* @param lContainer to search for views
|
|
|
|
* @param tContainerNode to search for views
|
2018-03-08 12:10:20 +01:00
|
|
|
* @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(
|
2018-09-17 14:32:45 -07:00
|
|
|
lContainer: LContainer, tContainerNode: TContainerNode, startIdx: number,
|
2018-11-21 21:14:06 -08:00
|
|
|
viewBlockId: number): LView|null {
|
2018-09-17 14:32:45 -07:00
|
|
|
const views = lContainer[VIEWS];
|
2018-03-08 12:10:20 +01:00
|
|
|
for (let i = startIdx; i < views.length; i++) {
|
2018-09-12 08:47:03 -07:00
|
|
|
const viewAtPositionId = views[i][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
|
2018-09-17 14:32:45 -07:00
|
|
|
removeView(lContainer, tContainerNode, i);
|
2018-03-08 12:10:20 +01:00
|
|
|
} 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
|
2018-10-18 09:23:18 +02:00
|
|
|
// later positions in the views array - stop the searchdef.cont here
|
2018-03-08 12:10:20 +01:00
|
|
|
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-08-18 11:14:50 -07:00
|
|
|
export function embeddedViewStart(viewBlockId: number, consts: number, vars: number): RenderFlags {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
2018-10-18 09:23:18 +02:00
|
|
|
const previousOrParentTNode = getPreviousOrParentTNode();
|
2018-09-05 16:15:37 -07:00
|
|
|
// The previous node can be a view node if we are processing an inline for loop
|
|
|
|
const containerTNode = previousOrParentTNode.type === TNodeType.View ?
|
|
|
|
previousOrParentTNode.parent ! :
|
|
|
|
previousOrParentTNode;
|
2018-11-21 21:14:06 -08:00
|
|
|
const lContainer = lView[containerTNode.index] as LContainer;
|
2018-09-05 16:15:37 -07:00
|
|
|
|
|
|
|
ngDevMode && assertNodeType(containerTNode, TNodeType.Container);
|
2018-09-13 16:07:23 -07:00
|
|
|
let viewToRender = scanForView(
|
2018-09-17 14:32:45 -07:00
|
|
|
lContainer, containerTNode as TContainerNode, lContainer[ACTIVE_INDEX] !, viewBlockId);
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-09-12 08:47:03 -07:00
|
|
|
if (viewToRender) {
|
2018-10-18 09:23:18 +02:00
|
|
|
setIsParent(true);
|
2018-09-12 08:47:03 -07:00
|
|
|
enterView(viewToRender, viewToRender[TVIEW].node);
|
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-11-21 21:14:06 -08:00
|
|
|
viewToRender = createLView(
|
|
|
|
lView,
|
2018-09-05 16:15:37 -07:00
|
|
|
getOrCreateEmbeddedTView(viewBlockId, consts, vars, containerTNode as TContainerNode), null,
|
2018-11-21 21:14:06 -08:00
|
|
|
LViewFlags.CheckAlways);
|
2018-05-28 11:57:36 +02:00
|
|
|
|
2018-06-07 22:42:32 -07:00
|
|
|
if (lContainer[QUERIES]) {
|
2018-09-12 08:47:03 -07:00
|
|
|
viewToRender[QUERIES] = lContainer[QUERIES] !.createView();
|
2018-01-17 17:55:55 +01:00
|
|
|
}
|
|
|
|
|
2019-01-24 23:50:12 +00:00
|
|
|
const tParentNode = getIsParent() ? previousOrParentTNode :
|
|
|
|
previousOrParentTNode && previousOrParentTNode.parent;
|
|
|
|
assignTViewNodeToLView(viewToRender[TVIEW], tParentNode, viewBlockId, viewToRender);
|
2018-09-12 08:47:03 -07:00
|
|
|
enterView(viewToRender, viewToRender[TVIEW].node);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-10-11 13:13:57 -07:00
|
|
|
if (lContainer) {
|
2018-12-18 16:58:51 -08:00
|
|
|
if (isCreationMode(viewToRender)) {
|
2018-06-22 15:37:38 +02:00
|
|
|
// it is a new view, insert it into collection of views for a given container
|
2018-11-21 21:14:06 -08:00
|
|
|
insertView(viewToRender, lContainer, lView, lContainer[ACTIVE_INDEX] !, -1);
|
2018-06-22 15:37:38 +02:00
|
|
|
}
|
|
|
|
lContainer[ACTIVE_INDEX] !++;
|
|
|
|
}
|
2018-12-18 16:58:51 -08:00
|
|
|
return isCreationMode(viewToRender) ? RenderFlags.Create | RenderFlags.Update :
|
|
|
|
RenderFlags.Update;
|
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
|
2018-08-16 18:53:21 -07:00
|
|
|
* @param consts The number of nodes, local refs, and pipes in this template
|
2018-08-18 11:14:50 -07:00
|
|
|
* @param vars The number of bindings and pure function bindings in this template
|
2018-09-05 16:15:37 -07:00
|
|
|
* @param container 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-08-16 18:53:21 -07:00
|
|
|
function getOrCreateEmbeddedTView(
|
2018-09-05 16:15:37 -07:00
|
|
|
viewIndex: number, consts: number, vars: number, parent: TContainerNode): TView {
|
2018-11-21 21:14:06 -08:00
|
|
|
const tView = getLView()[TVIEW];
|
2018-05-17 12:54:57 -07:00
|
|
|
ngDevMode && assertNodeType(parent, TNodeType.Container);
|
2018-09-05 16:15:37 -07:00
|
|
|
const containerTViews = parent.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-08-18 11:14:50 -07:00
|
|
|
containerTViews[viewIndex] = createTView(
|
|
|
|
viewIndex, null, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null);
|
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-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const viewHost = lView[HOST_NODE];
|
2018-12-18 16:58:51 -08:00
|
|
|
|
|
|
|
if (isCreationMode(lView)) {
|
|
|
|
refreshDescendantViews(lView); // creation mode pass
|
|
|
|
lView[FLAGS] &= ~LViewFlags.CreationMode;
|
|
|
|
}
|
|
|
|
refreshDescendantViews(lView); // update mode pass
|
2018-11-21 21:14:06 -08:00
|
|
|
leaveView(lView[PARENT] !);
|
2018-10-18 09:23:18 +02:00
|
|
|
setPreviousOrParentTNode(viewHost !);
|
|
|
|
setIsParent(false);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-01-17 10:09:05 -08: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
|
|
|
*
|
2018-11-21 21:14:06 -08:00
|
|
|
* @param adjustedElementIndex Element index in LView[] (adjusted for HEADER_OFFSET)
|
2017-12-14 16:26:28 -08:00
|
|
|
*/
|
2018-12-18 16:58:51 -08:00
|
|
|
export function componentRefresh<T>(adjustedElementIndex: number): void {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
ngDevMode && assertDataInRange(lView, adjustedElementIndex);
|
|
|
|
const hostView = getComponentViewByIndex(adjustedElementIndex, lView);
|
|
|
|
ngDevMode && assertNodeType(lView[TVIEW].data[adjustedElementIndex] as TNode, TNodeType.Element);
|
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
|
2018-06-07 22:42:32 -07:00
|
|
|
if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
|
2018-11-26 15:27:59 -08:00
|
|
|
syncViewWithBlueprint(hostView);
|
2018-12-18 16:58:51 -08:00
|
|
|
checkView(hostView, hostView[CONTEXT]);
|
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-10-04 13:28:39 -07:00
|
|
|
/**
|
2018-11-21 21:14:06 -08:00
|
|
|
* Syncs an LView instance with its blueprint if they have gotten out of sync.
|
2018-10-04 13:28:39 -07:00
|
|
|
*
|
|
|
|
* Typically, blueprints and their view instances should always be in sync, so the loop here
|
|
|
|
* will be skipped. However, consider this case of two components side-by-side:
|
|
|
|
*
|
|
|
|
* App template:
|
|
|
|
* ```
|
|
|
|
* <comp></comp>
|
|
|
|
* <comp></comp>
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* The following will happen:
|
|
|
|
* 1. App template begins processing.
|
2018-11-21 21:14:06 -08:00
|
|
|
* 2. First <comp> is matched as a component and its LView is created.
|
|
|
|
* 3. Second <comp> is matched as a component and its LView is created.
|
2018-10-04 13:28:39 -07:00
|
|
|
* 4. App template completes processing, so it's time to check child templates.
|
|
|
|
* 5. First <comp> template is checked. It has a directive, so its def is pushed to blueprint.
|
|
|
|
* 6. Second <comp> template is checked. Its blueprint has been updated by the first
|
2018-11-21 21:14:06 -08:00
|
|
|
* <comp> template, but its LView was created before this update, so it is out of sync.
|
2018-10-04 13:28:39 -07:00
|
|
|
*
|
|
|
|
* Note that embedded views inside ngFor loops will never be out of sync because these views
|
|
|
|
* are processed as soon as they are created.
|
|
|
|
*
|
|
|
|
* @param componentView The view to sync
|
|
|
|
*/
|
2018-11-21 21:14:06 -08:00
|
|
|
function syncViewWithBlueprint(componentView: LView) {
|
2018-10-04 13:28:39 -07:00
|
|
|
const componentTView = componentView[TVIEW];
|
|
|
|
for (let i = componentView.length; i < componentTView.blueprint.length; i++) {
|
|
|
|
componentView[i] = componentTView.blueprint[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-08 16:55:47 -08:00
|
|
|
/** Returns a boolean for whether the view is attached */
|
2018-11-21 21:14:06 -08:00
|
|
|
export function viewAttached(view: LView): boolean {
|
2018-06-07 22:42:32 -07:00
|
|
|
return (view[FLAGS] & LViewFlags.Attached) === LViewFlags.Attached;
|
2018-03-08 16:55:47 -08:00
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
/**
|
|
|
|
* Instruction to distribute projectable nodes among <ng-content> occurrences in a given template.
|
|
|
|
* It takes all the selectors from the entire component's template and decides where
|
|
|
|
* each projected node belongs (it re-distributes nodes among "buckets" where each "bucket" is
|
|
|
|
* backed by a selector).
|
|
|
|
*
|
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-07-03 20:04:36 -07:00
|
|
|
export function projectionDef(selectors?: CssSelectorList[], textSelectors?: string[]): void {
|
2018-11-21 21:14:06 -08:00
|
|
|
const componentNode = findComponentView(getLView())[HOST_NODE] as TElementNode;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-09-13 16:07:23 -07:00
|
|
|
if (!componentNode.projection) {
|
2018-07-03 20:04:36 -07:00
|
|
|
const noOfNodeBuckets = selectors ? selectors.length + 1 : 1;
|
2018-09-13 16:07:23 -07:00
|
|
|
const pData: (TNode | null)[] = componentNode.projection =
|
2018-07-03 20:04:36 -07:00
|
|
|
new Array(noOfNodeBuckets).fill(null);
|
|
|
|
const tails: (TNode | null)[] = pData.slice();
|
2018-01-31 15:50:24 +01:00
|
|
|
|
2018-09-13 16:07:23 -07:00
|
|
|
let componentChild: TNode|null = componentNode.child;
|
2018-07-03 20:04:36 -07:00
|
|
|
|
|
|
|
while (componentChild !== null) {
|
|
|
|
const bucketIndex =
|
|
|
|
selectors ? matchingSelectorIndex(componentChild, selectors, textSelectors !) : 0;
|
|
|
|
const nextNode = componentChild.next;
|
|
|
|
|
|
|
|
if (tails[bucketIndex]) {
|
|
|
|
tails[bucketIndex] !.next = componentChild;
|
|
|
|
} else {
|
|
|
|
pData[bucketIndex] = componentChild;
|
|
|
|
}
|
2019-01-15 14:46:10 +01:00
|
|
|
componentChild.next = null;
|
2018-07-03 20:04:36 -07:00
|
|
|
tails[bucketIndex] = componentChild;
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-07-03 20:04:36 -07:00
|
|
|
componentChild = nextNode;
|
|
|
|
}
|
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-01-25 15:32:21 +01:00
|
|
|
/**
|
2018-07-03 20:04:36 -07:00
|
|
|
* Stack used to keep track of projection nodes in projection() instruction.
|
2018-01-25 15:32:21 +01:00
|
|
|
*
|
2018-07-03 20:04:36 -07:00
|
|
|
* This is deliberately created outside of projection() to avoid allocating
|
|
|
|
* a new array each time the function is called. Instead the array will be
|
|
|
|
* re-used by each invocation. This works because the function is not reentrant.
|
2018-01-25 15:32:21 +01:00
|
|
|
*/
|
2018-11-21 21:14:06 -08:00
|
|
|
const projectionNodeStack: (LView | TNode)[] = [];
|
2018-01-25 15:32:21 +01:00
|
|
|
|
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
|
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-07-03 20:04:36 -07:00
|
|
|
export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?: string[]): void {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
2018-09-13 16:07:23 -07:00
|
|
|
const tProjectionNode =
|
2018-10-12 18:49:00 -07:00
|
|
|
createNodeAtIndex(nodeIndex, TNodeType.Projection, null, null, attrs || null);
|
2018-07-03 20:04:36 -07:00
|
|
|
|
|
|
|
// We can't use viewData[HOST_NODE] because projection nodes can be nested in embedded views.
|
2018-09-13 16:07:23 -07:00
|
|
|
if (tProjectionNode.projection === null) tProjectionNode.projection = selectorIndex;
|
2018-04-18 16:23:49 -07:00
|
|
|
|
2018-04-16 11:49:11 -07:00
|
|
|
// `<ng-content>` has no content
|
2018-10-18 09:23:18 +02:00
|
|
|
setIsParent(false);
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2018-07-03 20:04:36 -07:00
|
|
|
// re-distribution of projectable nodes is stored on a component's view level
|
2018-11-21 21:14:06 -08:00
|
|
|
const componentView = findComponentView(lView);
|
2018-09-13 16:07:23 -07:00
|
|
|
const componentNode = componentView[HOST_NODE] as TElementNode;
|
|
|
|
let nodeToProject = (componentNode.projection as(TNode | null)[])[selectorIndex];
|
|
|
|
let projectedView = componentView[PARENT] !;
|
2018-09-08 10:55:41 -07:00
|
|
|
let projectionNodeIndex = -1;
|
|
|
|
|
2019-01-21 14:55:37 +01:00
|
|
|
if (Array.isArray(nodeToProject)) {
|
|
|
|
appendChild(nodeToProject, tProjectionNode, lView);
|
|
|
|
} else {
|
|
|
|
while (nodeToProject) {
|
|
|
|
if (nodeToProject.type === TNodeType.Projection) {
|
|
|
|
// This node is re-projected, so we must go up the tree to get its projected nodes.
|
|
|
|
const currentComponentView = findComponentView(projectedView);
|
|
|
|
const currentComponentHost = currentComponentView[HOST_NODE] as TElementNode;
|
|
|
|
const firstProjectedNode = (currentComponentHost.projection as(
|
|
|
|
TNode | null)[])[nodeToProject.projection as number];
|
|
|
|
|
|
|
|
if (firstProjectedNode) {
|
|
|
|
if (Array.isArray(firstProjectedNode)) {
|
|
|
|
appendChild(firstProjectedNode, tProjectionNode, lView);
|
|
|
|
} else {
|
|
|
|
projectionNodeStack[++projectionNodeIndex] = nodeToProject;
|
|
|
|
projectionNodeStack[++projectionNodeIndex] = projectedView;
|
|
|
|
|
|
|
|
nodeToProject = firstProjectedNode;
|
|
|
|
projectedView = currentComponentView[PARENT] !;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// This flag must be set now or we won't know that this node is projected
|
|
|
|
// if the nodes are inserted into a container later.
|
|
|
|
nodeToProject.flags |= TNodeFlags.isProjected;
|
|
|
|
appendProjectedNode(nodeToProject, tProjectionNode, lView, projectedView);
|
2018-09-08 10:55:41 -07:00
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
|
2019-01-21 14:55:37 +01:00
|
|
|
// If we are finished with a list of re-projected nodes, we need to get
|
|
|
|
// back to the root projection node that was re-projected.
|
|
|
|
if (nodeToProject.next === null && projectedView !== componentView[PARENT] !) {
|
|
|
|
projectedView = projectionNodeStack[projectionNodeIndex--] as LView;
|
|
|
|
nodeToProject = projectionNodeStack[projectionNodeIndex--] as TNode;
|
|
|
|
}
|
|
|
|
nodeToProject = nodeToProject.next;
|
2018-07-03 20:04:36 -07:00
|
|
|
}
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-11-21 21:14:06 -08:00
|
|
|
* Adds LView or 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-11-21 21:14:06 -08:00
|
|
|
* @param lView The view where LView or LContainer should be added
|
|
|
|
* @param adjustedHostIndex Index of the view's host node in LView[], adjusted for header
|
|
|
|
* @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-11-21 21:14:06 -08:00
|
|
|
export function addToViewTree<T extends LView|LContainer>(
|
|
|
|
lView: LView, adjustedHostIndex: number, state: T): T {
|
|
|
|
const tView = lView[TVIEW];
|
|
|
|
if (lView[TAIL]) {
|
|
|
|
lView[TAIL] ![NEXT] = state;
|
2019-01-30 16:38:59 +01:00
|
|
|
} else if (tView.firstTemplatePass) {
|
2018-06-07 22:42:32 -07:00
|
|
|
tView.childIndex = adjustedHostIndex;
|
2018-05-30 13:43:14 -07:00
|
|
|
}
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[TAIL] = state;
|
2017-12-01 14:23:03 -08:00
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2018-02-23 13:17:20 -08:00
|
|
|
///////////////////////////////
|
|
|
|
//// Change detection
|
|
|
|
///////////////////////////////
|
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
/** If node is an OnPush component, marks its LView dirty. */
|
|
|
|
function markDirtyIfOnPush(lView: LView, viewIndex: number): void {
|
|
|
|
const childComponentLView = getComponentViewByIndex(viewIndex, lView);
|
|
|
|
if (!(childComponentLView[FLAGS] & LViewFlags.CheckAlways)) {
|
|
|
|
childComponentLView[FLAGS] |= LViewFlags.Dirty;
|
2018-02-23 13:17:20 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-14 15:58:57 -07:00
|
|
|
/** Wraps an event listener with preventDefault behavior. */
|
2018-10-18 09:23:18 +02:00
|
|
|
function wrapListenerWithPreventDefault(listenerFn: (e?: any) => any): EventListener {
|
2018-09-14 15:58:57 -07:00
|
|
|
return function wrapListenerIn_preventDefault(e: Event) {
|
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
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-01-10 10:52:04 -08:00
|
|
|
/**
|
|
|
|
* Marks current view and all ancestors dirty.
|
|
|
|
*
|
|
|
|
* Returns the root view because it is found as a byproduct of marking the view tree
|
|
|
|
* dirty, and can be used by methods that consume markViewDirty() to easily schedule
|
|
|
|
* change detection. Otherwise, such methods would need to traverse up the view tree
|
|
|
|
* an additional time to get the root view and schedule a tick on it.
|
|
|
|
*
|
|
|
|
* @param lView The starting LView to mark dirty
|
|
|
|
* @returns the root LView
|
|
|
|
*/
|
|
|
|
export function markViewDirty(lView: LView): LView {
|
2018-11-21 21:14:06 -08:00
|
|
|
while (lView && !(lView[FLAGS] & LViewFlags.IsRoot)) {
|
|
|
|
lView[FLAGS] |= LViewFlags.Dirty;
|
|
|
|
lView = lView[PARENT] !;
|
2018-02-23 13:17:20 -08:00
|
|
|
}
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[FLAGS] |= LViewFlags.Dirty;
|
2019-01-10 10:52:04 -08:00
|
|
|
return lView;
|
2018-08-28 16:49:52 -07:00
|
|
|
}
|
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.
|
|
|
|
*/
|
2018-10-03 14:09:59 -07:00
|
|
|
export function scheduleTick<T>(rootContext: RootContext, flags: RootContextFlags) {
|
|
|
|
const nothingScheduled = rootContext.flags === RootContextFlags.Empty;
|
|
|
|
rootContext.flags |= flags;
|
|
|
|
|
|
|
|
if (nothingScheduled && rootContext.clean == _CLEAN_PROMISE) {
|
2018-02-23 13:17:20 -08:00
|
|
|
let res: null|((val: null) => void);
|
|
|
|
rootContext.clean = new Promise<null>((r) => res = r);
|
|
|
|
rootContext.scheduler(() => {
|
2018-08-28 16:49:52 -07:00
|
|
|
if (rootContext.flags & RootContextFlags.DetectChanges) {
|
|
|
|
rootContext.flags &= ~RootContextFlags.DetectChanges;
|
|
|
|
tickRootContext(rootContext);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rootContext.flags & RootContextFlags.FlushPlayers) {
|
|
|
|
rootContext.flags &= ~RootContextFlags.FlushPlayers;
|
|
|
|
const playerHandler = rootContext.playerHandler;
|
|
|
|
if (playerHandler) {
|
|
|
|
playerHandler.flushPlayers();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-23 13:17:20 -08:00
|
|
|
rootContext.clean = _CLEAN_PROMISE;
|
2018-08-28 16:49:52 -07:00
|
|
|
res !(null);
|
2018-02-23 13:17:20 -08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-06-07 22:42:32 -07:00
|
|
|
const rootContext = rootView[CONTEXT] as RootContext;
|
2018-05-09 16:49:39 -07:00
|
|
|
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];
|
2018-12-18 16:58:51 -08:00
|
|
|
renderComponentOrTemplate(readPatchedLView(rootComponent) !, rootComponent);
|
2018-05-09 16:49:39 -07:00
|
|
|
}
|
2018-03-06 11:58:08 -08:00
|
|
|
}
|
|
|
|
|
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 {
|
2018-12-18 16:58:51 -08:00
|
|
|
const view = getComponentViewByInstance(component) !;
|
|
|
|
detectChangesInternal<T>(view, component);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function detectChangesInternal<T>(view: LView, context: T) {
|
|
|
|
const rendererFactory = view[RENDERER_FACTORY];
|
|
|
|
|
|
|
|
if (rendererFactory.begin) rendererFactory.begin();
|
|
|
|
|
|
|
|
if (isCreationMode(view)) {
|
|
|
|
checkView(view, context); // creation mode pass
|
|
|
|
}
|
|
|
|
checkView(view, context); // update mode pass
|
|
|
|
|
|
|
|
if (rendererFactory.end) rendererFactory.end();
|
2018-03-06 11:58:08 -08:00
|
|
|
}
|
|
|
|
|
2018-07-25 11:07:54 +02:00
|
|
|
/**
|
|
|
|
* Synchronously perform change detection on a root view and its components.
|
|
|
|
*
|
2018-11-21 21:14:06 -08:00
|
|
|
* @param lView The view which the change detection should be performed on.
|
2018-07-25 11:07:54 +02:00
|
|
|
*/
|
2018-11-21 21:14:06 -08:00
|
|
|
export function detectChangesInRootView(lView: LView): void {
|
|
|
|
tickRootContext(lView[CONTEXT] as RootContext);
|
2018-07-25 11:07:54 +02:00
|
|
|
}
|
|
|
|
|
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 {
|
2018-10-18 09:23:18 +02:00
|
|
|
setCheckNoChangesMode(true);
|
2018-03-09 20:22:18 -08:00
|
|
|
try {
|
|
|
|
detectChanges(component);
|
|
|
|
} finally {
|
2018-10-18 09:23:18 +02:00
|
|
|
setCheckNoChangesMode(false);
|
2018-03-09 20:22:18 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-25 11:07:54 +02:00
|
|
|
/**
|
|
|
|
* Checks the change detector on a root view and its components, and throws if any changes are
|
|
|
|
* detected.
|
|
|
|
*
|
|
|
|
* This is used in development mode to verify that running change detection doesn't
|
|
|
|
* introduce other changes.
|
|
|
|
*
|
2018-11-21 21:14:06 -08:00
|
|
|
* @param lView The view which the change detection should be checked on.
|
2018-07-25 11:07:54 +02:00
|
|
|
*/
|
2018-11-21 21:14:06 -08:00
|
|
|
export function checkNoChangesInRootView(lView: LView): void {
|
2018-10-18 09:23:18 +02:00
|
|
|
setCheckNoChangesMode(true);
|
2018-07-25 11:07:54 +02:00
|
|
|
try {
|
2018-11-21 21:14:06 -08:00
|
|
|
detectChangesInRootView(lView);
|
2018-07-25 11:07:54 +02:00
|
|
|
} finally {
|
2018-10-18 09:23:18 +02:00
|
|
|
setCheckNoChangesMode(false);
|
2018-07-25 11:07:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-12-18 16:58:51 -08:00
|
|
|
export function checkView<T>(hostView: LView, component: T) {
|
2018-06-19 17:58:42 +02:00
|
|
|
const hostTView = hostView[TVIEW];
|
2018-09-13 16:07:23 -07:00
|
|
|
const oldView = enterView(hostView, hostView[HOST_NODE]);
|
2018-08-15 18:37:03 -07:00
|
|
|
const templateFn = hostTView.template !;
|
2019-01-18 18:02:32 -08:00
|
|
|
const creationMode = isCreationMode(hostView);
|
2018-03-25 21:32:39 -07:00
|
|
|
|
2018-03-16 16:42:13 -07:00
|
|
|
try {
|
2018-06-08 09:00:01 -07:00
|
|
|
namespaceHTML();
|
2019-01-18 18:02:32 -08:00
|
|
|
creationMode && executeViewQueryFn(hostView, hostTView, component);
|
2018-12-18 16:58:51 -08:00
|
|
|
templateFn(getRenderFlags(hostView), component);
|
|
|
|
refreshDescendantViews(hostView);
|
2019-01-18 18:02:32 -08:00
|
|
|
!creationMode && executeViewQueryFn(hostView, hostTView, component);
|
2018-03-16 16:42:13 -07:00
|
|
|
} finally {
|
2018-12-18 16:58:51 -08:00
|
|
|
leaveView(oldView);
|
2018-03-06 11:58:08 -08:00
|
|
|
}
|
2018-02-23 13:17:20 -08:00
|
|
|
}
|
|
|
|
|
2019-01-18 18:02:32 -08:00
|
|
|
function executeViewQueryFn<T>(lView: LView, tView: TView, component: T): void {
|
|
|
|
const viewQuery = tView.viewQuery;
|
|
|
|
if (viewQuery) {
|
2019-01-23 11:54:43 -08:00
|
|
|
setCurrentQueryIndex(tView.viewQueryStartIndex);
|
2019-01-18 18:02:32 -08:00
|
|
|
viewQuery(getRenderFlags(lView), component);
|
2018-06-19 17:58:42 +02: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.
|
2018-11-15 08:43:56 -08:00
|
|
|
*
|
|
|
|
* @publicApi
|
2018-02-23 13:17:20 -08:00
|
|
|
*/
|
|
|
|
export function markDirty<T>(component: T) {
|
2018-05-09 16:49:39 -07:00
|
|
|
ngDevMode && assertDefined(component, 'component');
|
2019-01-10 10:52:04 -08:00
|
|
|
const rootView = markViewDirty(getComponentViewByInstance(component));
|
|
|
|
|
|
|
|
ngDevMode && assertDefined(rootView[CONTEXT], 'rootContext should be defined');
|
|
|
|
scheduleTick(rootView[CONTEXT] as RootContext, RootContextFlags.DetectChanges);
|
2018-02-23 13:17:20 -08:00
|
|
|
}
|
|
|
|
|
2018-02-14 11:22:14 -08:00
|
|
|
///////////////////////////////
|
|
|
|
//// Bindings & interpolations
|
|
|
|
///////////////////////////////
|
2017-12-01 14:23:03 -08:00
|
|
|
|
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 {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
2019-01-24 08:53:00 -08:00
|
|
|
const bindingIndex = lView[BINDING_INDEX]++;
|
|
|
|
storeBindingMetadata(lView);
|
|
|
|
return bindingUpdated(lView, bindingIndex, value) ? value : NO_CHANGE;
|
2018-02-14 11:22:14 -08:00
|
|
|
}
|
|
|
|
|
2018-11-27 12:05:26 -08:00
|
|
|
/**
|
|
|
|
* Allocates the necessary amount of slots for host vars.
|
|
|
|
*
|
|
|
|
* @param count Amount of vars to be allocated
|
|
|
|
*/
|
|
|
|
export function allocHostVars(count: number): void {
|
|
|
|
const lView = getLView();
|
|
|
|
const tView = lView[TVIEW];
|
2019-01-30 16:38:59 +01:00
|
|
|
if (!tView.firstTemplatePass) return;
|
2018-11-27 12:05:26 -08:00
|
|
|
queueHostBindingForCheck(tView, getCurrentDirectiveDef() !, count);
|
|
|
|
prefillHostVars(tView, lView, count);
|
|
|
|
}
|
|
|
|
|
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;
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
2019-01-24 08:53:00 -08:00
|
|
|
const tData = lView[TVIEW].data;
|
2018-11-21 21:14:06 -08:00
|
|
|
let bindingIndex = lView[BINDING_INDEX];
|
2019-01-24 08:53:00 -08:00
|
|
|
|
|
|
|
if (tData[bindingIndex] == null) {
|
|
|
|
// 2 is the index of the first static interstitial value (ie. not prefix)
|
|
|
|
for (let i = 2; i < values.length; i += 2) {
|
|
|
|
tData[bindingIndex++] = values[i];
|
|
|
|
}
|
|
|
|
bindingIndex = lView[BINDING_INDEX];
|
|
|
|
}
|
|
|
|
|
2018-02-16 21:20:55 -08:00
|
|
|
for (let i = 1; i < values.length; i += 2) {
|
|
|
|
// Check if bindings (odd indexes) have changed
|
2018-11-21 21:14:06 -08:00
|
|
|
bindingUpdated(lView, bindingIndex++, values[i]) && (different = true);
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[BINDING_INDEX] = bindingIndex;
|
2019-01-24 08:53:00 -08:00
|
|
|
storeBindingMetadata(lView, values[0], values[values.length - 1]);
|
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) {
|
2019-01-12 00:59:48 -08:00
|
|
|
content += renderStringify(values[i]) + values[i + 1];
|
2018-02-16 21:20:55 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
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-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
2019-01-24 08:53:00 -08:00
|
|
|
const different = bindingUpdated(lView, lView[BINDING_INDEX]++, v0);
|
|
|
|
storeBindingMetadata(lView, prefix, suffix);
|
2019-01-12 00:59:48 -08:00
|
|
|
return different ? prefix + renderStringify(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-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
2019-01-24 08:53:00 -08:00
|
|
|
const bindingIndex = lView[BINDING_INDEX];
|
|
|
|
const different = bindingUpdated2(lView, bindingIndex, v0, v1);
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[BINDING_INDEX] += 2;
|
2018-02-16 17:20:14 -08:00
|
|
|
|
2019-01-24 08:53:00 -08:00
|
|
|
// Only set static strings the first time (data will be null subsequent runs).
|
|
|
|
const data = storeBindingMetadata(lView, prefix, suffix);
|
|
|
|
if (data) {
|
|
|
|
lView[TVIEW].data[bindingIndex] = i0;
|
|
|
|
}
|
|
|
|
|
2019-01-12 00:59:48 -08:00
|
|
|
return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + suffix : NO_CHANGE;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-07-19 11:02:22 +02:00
|
|
|
/** Creates an interpolation binding with 3 expressions. */
|
2018-02-14 11:22:14 -08:00
|
|
|
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-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
2019-01-24 08:53:00 -08:00
|
|
|
const bindingIndex = lView[BINDING_INDEX];
|
|
|
|
const different = bindingUpdated3(lView, bindingIndex, v0, v1, v2);
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[BINDING_INDEX] += 3;
|
2018-02-16 17:20:14 -08:00
|
|
|
|
2019-01-24 08:53:00 -08:00
|
|
|
// Only set static strings the first time (data will be null subsequent runs).
|
|
|
|
const data = storeBindingMetadata(lView, prefix, suffix);
|
|
|
|
if (data) {
|
|
|
|
const tData = lView[TVIEW].data;
|
|
|
|
tData[bindingIndex] = i0;
|
|
|
|
tData[bindingIndex + 1] = i1;
|
|
|
|
}
|
|
|
|
|
2019-01-12 00:59:48 -08:00
|
|
|
return different ?
|
|
|
|
prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + suffix :
|
|
|
|
NO_CHANGE;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
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-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
2019-01-24 08:53:00 -08:00
|
|
|
const bindingIndex = lView[BINDING_INDEX];
|
|
|
|
const different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3);
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[BINDING_INDEX] += 4;
|
2018-02-16 17:20:14 -08:00
|
|
|
|
2019-01-24 08:53:00 -08:00
|
|
|
// Only set static strings the first time (data will be null subsequent runs).
|
|
|
|
const data = storeBindingMetadata(lView, prefix, suffix);
|
|
|
|
if (data) {
|
|
|
|
const tData = lView[TVIEW].data;
|
|
|
|
tData[bindingIndex] = i0;
|
|
|
|
tData[bindingIndex + 1] = i1;
|
|
|
|
tData[bindingIndex + 2] = i2;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
return different ?
|
2019-01-12 00:59:48 -08:00
|
|
|
prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 +
|
|
|
|
renderStringify(v3) + suffix :
|
2017-12-01 14:23:03 -08:00
|
|
|
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-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const bindingIndex = lView[BINDING_INDEX];
|
|
|
|
let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3);
|
|
|
|
different = bindingUpdated(lView, bindingIndex + 4, v4) || different;
|
|
|
|
lView[BINDING_INDEX] += 5;
|
2018-02-16 21:20:55 -08:00
|
|
|
|
2019-01-24 08:53:00 -08:00
|
|
|
// Only set static strings the first time (data will be null subsequent runs).
|
|
|
|
const data = storeBindingMetadata(lView, prefix, suffix);
|
|
|
|
if (data) {
|
|
|
|
const tData = lView[TVIEW].data;
|
|
|
|
tData[bindingIndex] = i0;
|
|
|
|
tData[bindingIndex + 1] = i1;
|
|
|
|
tData[bindingIndex + 2] = i2;
|
|
|
|
tData[bindingIndex + 3] = i3;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
return different ?
|
2019-01-12 00:59:48 -08:00
|
|
|
prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 +
|
|
|
|
renderStringify(v3) + i3 + renderStringify(v4) + suffix :
|
2017-12-01 14:23:03 -08:00
|
|
|
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-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const bindingIndex = lView[BINDING_INDEX];
|
|
|
|
let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3);
|
|
|
|
different = bindingUpdated2(lView, bindingIndex + 4, v4, v5) || different;
|
|
|
|
lView[BINDING_INDEX] += 6;
|
2018-02-16 21:20:55 -08:00
|
|
|
|
2019-01-24 08:53:00 -08:00
|
|
|
// Only set static strings the first time (data will be null subsequent runs).
|
|
|
|
const data = storeBindingMetadata(lView, prefix, suffix);
|
|
|
|
if (data) {
|
|
|
|
const tData = lView[TVIEW].data;
|
|
|
|
tData[bindingIndex] = i0;
|
|
|
|
tData[bindingIndex + 1] = i1;
|
|
|
|
tData[bindingIndex + 2] = i2;
|
|
|
|
tData[bindingIndex + 3] = i3;
|
|
|
|
tData[bindingIndex + 4] = i4;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
return different ?
|
2019-01-12 00:59:48 -08:00
|
|
|
prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 +
|
|
|
|
renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + suffix :
|
2017-12-01 14:23:03 -08:00
|
|
|
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-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const bindingIndex = lView[BINDING_INDEX];
|
|
|
|
let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3);
|
|
|
|
different = bindingUpdated3(lView, bindingIndex + 4, v4, v5, v6) || different;
|
|
|
|
lView[BINDING_INDEX] += 7;
|
2018-02-16 17:20:14 -08:00
|
|
|
|
2019-01-24 08:53:00 -08:00
|
|
|
// Only set static strings the first time (data will be null subsequent runs).
|
|
|
|
const data = storeBindingMetadata(lView, prefix, suffix);
|
|
|
|
if (data) {
|
|
|
|
const tData = lView[TVIEW].data;
|
|
|
|
tData[bindingIndex] = i0;
|
|
|
|
tData[bindingIndex + 1] = i1;
|
|
|
|
tData[bindingIndex + 2] = i2;
|
|
|
|
tData[bindingIndex + 3] = i3;
|
|
|
|
tData[bindingIndex + 4] = i4;
|
|
|
|
tData[bindingIndex + 5] = i5;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
return different ?
|
2019-01-12 00:59:48 -08:00
|
|
|
prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 +
|
|
|
|
renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + i5 +
|
|
|
|
renderStringify(v6) + suffix :
|
2017-12-01 14:23:03 -08:00
|
|
|
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-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const bindingIndex = lView[BINDING_INDEX];
|
|
|
|
let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3);
|
|
|
|
different = bindingUpdated4(lView, bindingIndex + 4, v4, v5, v6, v7) || different;
|
|
|
|
lView[BINDING_INDEX] += 8;
|
2018-02-16 17:20:14 -08:00
|
|
|
|
2019-01-24 08:53:00 -08:00
|
|
|
// Only set static strings the first time (data will be null subsequent runs).
|
|
|
|
const data = storeBindingMetadata(lView, prefix, suffix);
|
|
|
|
if (data) {
|
|
|
|
const tData = lView[TVIEW].data;
|
|
|
|
tData[bindingIndex] = i0;
|
|
|
|
tData[bindingIndex + 1] = i1;
|
|
|
|
tData[bindingIndex + 2] = i2;
|
|
|
|
tData[bindingIndex + 3] = i3;
|
|
|
|
tData[bindingIndex + 4] = i4;
|
|
|
|
tData[bindingIndex + 5] = i5;
|
|
|
|
tData[bindingIndex + 6] = i6;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:23:03 -08:00
|
|
|
return different ?
|
2019-01-12 00:59:48 -08:00
|
|
|
prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 +
|
|
|
|
renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + i5 +
|
|
|
|
renderStringify(v6) + i6 + renderStringify(v7) + suffix :
|
2017-12-01 14:23:03 -08:00
|
|
|
NO_CHANGE;
|
|
|
|
}
|
|
|
|
|
2019-01-24 08:53:00 -08:00
|
|
|
/**
|
|
|
|
* Creates binding metadata for a particular binding and stores it in
|
|
|
|
* TView.data. These are generated in order to support DebugElement.properties.
|
|
|
|
*
|
|
|
|
* Each binding / interpolation will have one (including attribute bindings)
|
|
|
|
* because at the time of binding, we don't know to which instruction the binding
|
|
|
|
* belongs. It is always stored in TView.data at the index of the last binding
|
|
|
|
* value in LView (e.g. for interpolation8, it would be stored at the index of
|
|
|
|
* the 8th value).
|
|
|
|
*
|
|
|
|
* @param lView The LView that contains the current binding index.
|
|
|
|
* @param prefix The static prefix string
|
|
|
|
* @param suffix The static suffix string
|
|
|
|
*
|
|
|
|
* @returns Newly created binding metadata string for this binding or null
|
|
|
|
*/
|
|
|
|
function storeBindingMetadata(lView: LView, prefix = '', suffix = ''): string|null {
|
|
|
|
const tData = lView[TVIEW].data;
|
|
|
|
const lastBindingIndex = lView[BINDING_INDEX] - 1;
|
|
|
|
const value = INTERPOLATION_DELIMITER + prefix + INTERPOLATION_DELIMITER + suffix;
|
|
|
|
|
|
|
|
return tData[lastBindingIndex] == null ? (tData[lastBindingIndex] = value) : null;
|
|
|
|
}
|
|
|
|
|
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 {
|
2018-11-21 21:14:06 -08:00
|
|
|
const lView = getLView();
|
|
|
|
const tView = lView[TVIEW];
|
2018-02-16 16:58:07 -08:00
|
|
|
// We don't store any static data for local variables, so the first time
|
|
|
|
// we see the template, we should store as null to avoid a sparse array
|
2018-06-07 22:42:32 -07:00
|
|
|
const adjustedIndex = index + HEADER_OFFSET;
|
|
|
|
if (adjustedIndex >= tView.data.length) {
|
|
|
|
tView.data[adjustedIndex] = null;
|
2019-01-23 21:11:13 -08:00
|
|
|
tView.blueprint[adjustedIndex] = null;
|
2018-02-16 16:58:07 -08:00
|
|
|
}
|
2018-11-21 21:14:06 -08:00
|
|
|
lView[adjustedIndex] = value;
|
2017-12-01 14:23:03 -08:00
|
|
|
}
|
|
|
|
|
2018-07-25 17:25:22 -07:00
|
|
|
/**
|
|
|
|
* Retrieves a local reference from the current contextViewData.
|
|
|
|
*
|
|
|
|
* If the reference to retrieve is in a parent view, this instruction is used in conjunction
|
|
|
|
* with a nextContext() call, which walks up the tree and updates the contextViewData instance.
|
|
|
|
*
|
|
|
|
* @param index The index of the local ref in contextViewData.
|
|
|
|
*/
|
|
|
|
export function reference<T>(index: number) {
|
2018-11-21 21:14:06 -08:00
|
|
|
const contextLView = getContextLView();
|
|
|
|
return loadInternal<T>(contextLView, index);
|
2018-07-25 17:25:22 -07:00
|
|
|
}
|
|
|
|
|
2018-07-11 09:56:47 -07:00
|
|
|
/** Retrieves a value from current `viewData`. */
|
|
|
|
export function load<T>(index: number): T {
|
2018-11-21 21:14:06 -08:00
|
|
|
return loadInternal<T>(getLView(), index);
|
2018-07-11 09:56:47 -07:00
|
|
|
}
|
|
|
|
|
2018-02-14 13:37:54 -08:00
|
|
|
|
2018-10-18 09:23:18 +02:00
|
|
|
|
|
|
|
///////////////////////////////
|
|
|
|
//// DI
|
|
|
|
///////////////////////////////
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the value associated to the given token from the injectors.
|
|
|
|
*
|
|
|
|
* `directiveInject` is intended to be used for directive, component and pipe factories.
|
|
|
|
* All other injection use `inject` which does not walk the node injector tree.
|
|
|
|
*
|
|
|
|
* Usage example (in factory function):
|
|
|
|
*
|
|
|
|
* class SomeDirective {
|
|
|
|
* constructor(directive: DirectiveA) {}
|
|
|
|
*
|
|
|
|
* static ngDirectiveDef = defineDirective({
|
|
|
|
* type: SomeDirective,
|
|
|
|
* factory: () => new SomeDirective(directiveInject(DirectiveA))
|
|
|
|
* });
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* @param token the type or token to inject
|
|
|
|
* @param flags Injection flags
|
|
|
|
* @returns the value from the injector or `null` when not found
|
|
|
|
*/
|
|
|
|
export function directiveInject<T>(token: Type<T>| InjectionToken<T>): T;
|
|
|
|
export function directiveInject<T>(token: Type<T>| InjectionToken<T>, flags: InjectFlags): T;
|
|
|
|
export function directiveInject<T>(
|
|
|
|
token: Type<T>| InjectionToken<T>, flags = InjectFlags.Default): T|null {
|
2018-11-12 18:52:51 -08:00
|
|
|
token = resolveForwardRef(token);
|
2018-10-18 09:23:18 +02:00
|
|
|
return getOrCreateInjectable<T>(
|
|
|
|
getPreviousOrParentTNode() as TElementNode | TContainerNode | TElementContainerNode,
|
2018-11-21 21:14:06 -08:00
|
|
|
getLView(), token, flags);
|
2018-10-18 09:23:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Facade for the attribute injection from DI.
|
|
|
|
*/
|
2018-11-22 16:03:26 +01:00
|
|
|
export function injectAttribute(attrNameToInject: string): string|null {
|
2018-10-18 09:23:18 +02:00
|
|
|
return injectAttributeImpl(getPreviousOrParentTNode(), attrNameToInject);
|
2018-02-16 16:23:27 +01:00
|
|
|
}
|
|
|
|
|
2018-02-23 13:17:20 -08:00
|
|
|
export const CLEAN_PROMISE = _CLEAN_PROMISE;
|
2018-10-18 14:47:53 -07:00
|
|
|
|
2018-12-20 17:23:25 -08:00
|
|
|
function initializeTNodeInputs(tNode: TNode | null): PropertyAliases|null {
|
2018-10-18 14:47:53 -07:00
|
|
|
// If tNode.inputs is undefined, a listener has created outputs, but inputs haven't
|
|
|
|
// yet been checked.
|
|
|
|
if (tNode) {
|
|
|
|
if (tNode.inputs === undefined) {
|
|
|
|
// mark inputs as checked
|
2018-11-28 15:54:38 -08:00
|
|
|
tNode.inputs = generatePropertyAliases(tNode, BindingDirection.Input);
|
2018-10-18 14:47:53 -07:00
|
|
|
}
|
|
|
|
return tNode.inputs;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the current OpaqueViewState instance.
|
|
|
|
*
|
|
|
|
* Used in conjunction with the restoreView() instruction to save a snapshot
|
|
|
|
* of the current view and restore it when listeners are invoked. This allows
|
|
|
|
* walking the declaration view tree in listeners to get vars from parent views.
|
|
|
|
*/
|
|
|
|
export function getCurrentView(): OpaqueViewState {
|
|
|
|
return getLView() as any as OpaqueViewState;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getCleanup(view: LView): any[] {
|
|
|
|
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
|
|
|
return view[CLEANUP] || (view[CLEANUP] = []);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getTViewCleanup(view: LView): any[] {
|
|
|
|
return view[TVIEW].cleanup || (view[TVIEW].cleanup = []);
|
|
|
|
}
|
2019-01-17 11:09:13 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* There are cases where the sub component's renderer needs to be included
|
|
|
|
* instead of the current renderer (see the componentSyntheticHost* instructions).
|
|
|
|
*/
|
|
|
|
function loadComponentRenderer(tNode: TNode, lView: LView): Renderer3 {
|
|
|
|
const componentLView = lView[tNode.index] as LView;
|
|
|
|
return componentLView[RENDERER];
|
2019-01-23 21:11:13 -08:00
|
|
|
}
|