refactor(core): Create `TNodeType.Text` to display full template in `TView` debug (#39233)

When looking at `TView` debug template only Element nodes were displayed
as `TNode.Element` was used for both `RElement` and `RText`.
Additionally no text was stored in `TNode.value`. The result was that
the whole template could not be reconstructed. This refactoring creates
`TNodeType.Text` and store the text value in `TNode.value`.

The refactoring also changes `TNodeType` into flag-like structure make
it more efficient to check many different types at once.

PR Close #39233
This commit is contained in:
Misko Hevery 2020-10-12 21:38:06 -07:00 committed by Alex Rickabaugh
parent d50df92568
commit 70f1e2e04a
26 changed files with 264 additions and 248 deletions

View File

@ -529,7 +529,7 @@ function _queryNodeChildrenR3(
ngDevMode && assertTNodeForLView(tNode, lView); ngDevMode && assertTNodeForLView(tNode, lView);
const nativeNode = getNativeByTNodeOrNull(tNode, lView); const nativeNode = getNativeByTNodeOrNull(tNode, lView);
// For each type of TNode, specific logic is executed. // For each type of TNode, specific logic is executed.
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) { if (tNode.type & (TNodeType.AnyRNode | TNodeType.ElementContainer)) {
// Case 1: the TNode is an element // Case 1: the TNode is an element
// The native node has to be checked. // The native node has to be checked.
_addQueryMatchR3(nativeNode, predicate, matches, elementsOnly, rootNativeNode); _addQueryMatchR3(nativeNode, predicate, matches, elementsOnly, rootNativeNode);
@ -565,14 +565,14 @@ function _queryNodeChildrenR3(
_queryNodeChildrenInContainerR3( _queryNodeChildrenInContainerR3(
nodeOrContainer, predicate, matches, elementsOnly, rootNativeNode); nodeOrContainer, predicate, matches, elementsOnly, rootNativeNode);
} }
} else if (tNode.type === TNodeType.Container) { } else if (tNode.type & TNodeType.Container) {
// Case 2: the TNode is a container // Case 2: the TNode is a container
// The native node has to be checked. // The native node has to be checked.
const lContainer = lView[tNode.index]; const lContainer = lView[tNode.index];
_addQueryMatchR3(lContainer[NATIVE], predicate, matches, elementsOnly, rootNativeNode); _addQueryMatchR3(lContainer[NATIVE], predicate, matches, elementsOnly, rootNativeNode);
// Each view inside the container has to be processed. // Each view inside the container has to be processed.
_queryNodeChildrenInContainerR3(lContainer, predicate, matches, elementsOnly, rootNativeNode); _queryNodeChildrenInContainerR3(lContainer, predicate, matches, elementsOnly, rootNativeNode);
} else if (tNode.type === TNodeType.Projection) { } else if (tNode.type & TNodeType.Projection) {
// Case 3: the TNode is a projection insertion point (i.e. a <ng-content>). // Case 3: the TNode is a projection insertion point (i.e. a <ng-content>).
// The nodes projected at this location all need to be processed. // The nodes projected at this location all need to be processed.
const componentView = lView![DECLARATION_COMPONENT_VIEW]; const componentView = lView![DECLARATION_COMPONENT_VIEW];

View File

@ -13,7 +13,7 @@ import {injectRootLimpMode, setInjectImplementation} from '../di/injector_compat
import {getInjectorDef} from '../di/interface/defs'; import {getInjectorDef} from '../di/interface/defs';
import {InjectFlags} from '../di/interface/injector'; import {InjectFlags} from '../di/interface/injector';
import {Type} from '../interface/type'; import {Type} from '../interface/type';
import {assertDefined, assertEqual, assertIndexInRange, throwError} from '../util/assert'; import {assertDefined, assertEqual, assertIndexInRange} from '../util/assert';
import {noSideEffects} from '../util/closure'; import {noSideEffects} from '../util/closure';
import {assertDirectiveDef, assertNodeInjector, assertTNodeForLView} from './assert'; import {assertDirectiveDef, assertNodeInjector, assertTNodeForLView} from './assert';
@ -25,7 +25,7 @@ import {isFactory, NO_PARENT_INJECTOR, NodeInjectorFactory, NodeInjectorOffset,
import {AttributeMarker, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNode, TNodeProviderIndexes, TNodeType} from './interfaces/node'; import {AttributeMarker, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNode, TNodeProviderIndexes, TNodeType} from './interfaces/node';
import {isComponentDef, isComponentHost} from './interfaces/type_checks'; import {isComponentDef, isComponentHost} from './interfaces/type_checks';
import {DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, INJECTOR, LView, T_HOST, TData, TVIEW, TView, TViewType} from './interfaces/view'; import {DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, INJECTOR, LView, T_HOST, TData, TVIEW, TView, TViewType} from './interfaces/view';
import {assertNodeOfPossibleTypes} from './node_assert'; import {assertTNodeType} from './node_assert';
import {enterDI, leaveDI} from './state'; import {enterDI, leaveDI} from './state';
import {isNameOnlyAttributeMarker} from './util/attrs_utils'; import {isNameOnlyAttributeMarker} from './util/attrs_utils';
import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from './util/injector_utils'; import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from './util/injector_utils';
@ -301,9 +301,7 @@ export function diPublicInInjector(
* @publicApi * @publicApi
*/ */
export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): string|null { export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): string|null {
ngDevMode && ngDevMode && assertTNodeType(tNode, TNodeType.AnyContainer | TNodeType.AnyRNode);
assertNodeOfPossibleTypes(
tNode, [TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer]);
ngDevMode && assertDefined(tNode, 'expecting tNode'); ngDevMode && assertDefined(tNode, 'expecting tNode');
if (attrNameToInject === 'class') { if (attrNameToInject === 'class') {
return tNode.classes; return tNode.classes;
@ -506,7 +504,7 @@ function searchTokensOnInjector<T>(
// - AND the parent TNode is an Element. // - AND the parent TNode is an Element.
// This means that we just came from the Component's View and therefore are allowed to see // This means that we just came from the Component's View and therefore are allowed to see
// into the ViewProviders. // into the ViewProviders.
(previousTView != currentTView && (tNode.type === TNodeType.Element)); (previousTView != currentTView && ((tNode.type & TNodeType.AnyRNode) !== 0));
// This special case happens when there is a @host on the inject and when we are searching // This special case happens when there is a @host on the inject and when we are searching
// on the host element node. // on the host element node.

View File

@ -10,7 +10,7 @@ import {getPluralCase} from '../../i18n/localization';
import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertIndexInRange, throwError} from '../../util/assert'; import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertIndexInRange, throwError} from '../../util/assert';
import {assertIndexInExpandoRange, assertTIcu} from '../assert'; import {assertIndexInExpandoRange, assertTIcu} from '../assert';
import {attachPatchData} from '../context_discovery'; import {attachPatchData} from '../context_discovery';
import {elementPropertyInternal, setElementAttribute, textBindingInternal} from '../instructions/shared'; import {elementPropertyInternal, setElementAttribute} from '../instructions/shared';
import {COMMENT_MARKER, ELEMENT_MARKER, getCurrentICUCaseIndex, getParentFromI18nMutateOpCode, getRefFromI18nMutateOpCode, I18nCreateOpCode, I18nCreateOpCodes, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from '../interfaces/i18n'; import {COMMENT_MARKER, ELEMENT_MARKER, getCurrentICUCaseIndex, getParentFromI18nMutateOpCode, getRefFromI18nMutateOpCode, I18nCreateOpCode, I18nCreateOpCodes, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from '../interfaces/i18n';
import {TNode} from '../interfaces/node'; import {TNode} from '../interfaces/node';
import {RElement, RNode, RText} from '../interfaces/renderer'; import {RElement, RNode, RText} from '../interfaces/renderer';

View File

@ -63,7 +63,7 @@ export function addTNodeAndUpdateInsertBeforeIndex(previousTNodes: TNode[], newT
} }
function isI18nText(tNode: TNode): boolean { function isI18nText(tNode: TNode): boolean {
return tNode.type !== TNodeType.Placeholder; return !(tNode.type & TNodeType.Placeholder);
} }
function isNewTNodeCreatedBefore(existingTNode: TNode, newTNode: TNode): boolean { function isNewTNodeCreatedBefore(existingTNode: TNode, newTNode: TNode): boolean {

View File

@ -17,7 +17,7 @@ import {loadIcuContainerVisitor} from '../instructions/i18n_icu_container_visito
import {allocExpando, createTNodeAtIndex, elementAttributeInternal, setInputsForProperty, setNgReflectProperties} from '../instructions/shared'; import {allocExpando, createTNodeAtIndex, elementAttributeInternal, setInputsForProperty, setNgReflectProperties} from '../instructions/shared';
import {getDocument} from '../interfaces/document'; import {getDocument} from '../interfaces/document';
import {COMMENT_MARKER, ELEMENT_MARKER, ensureIcuContainerVisitorLoaded, I18nCreateOpCode, I18nCreateOpCodes, I18nMutateOpCode, i18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuExpression, IcuType, TI18n, TIcu} from '../interfaces/i18n'; import {COMMENT_MARKER, ELEMENT_MARKER, ensureIcuContainerVisitorLoaded, I18nCreateOpCode, I18nCreateOpCodes, I18nMutateOpCode, i18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuExpression, IcuType, TI18n, TIcu} from '../interfaces/i18n';
import {TIcuContainerNode, TNode, TNodeType} from '../interfaces/node'; import {TNode, TNodeType} from '../interfaces/node';
import {RComment, RElement} from '../interfaces/renderer'; import {RComment, RElement} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization'; import {SanitizerFn} from '../interfaces/sanitization';
import {HEADER_OFFSET, LView, TView} from '../interfaces/view'; import {HEADER_OFFSET, LView, TView} from '../interfaces/view';
@ -152,7 +152,7 @@ export function i18nStartFirstCreatePass(
*/ */
function createTNodeAndAddOpCode( function createTNodeAndAddOpCode(
tView: TView, rootTNode: TNode|null, existingTNodes: TNode[], lView: LView, tView: TView, rootTNode: TNode|null, existingTNodes: TNode[], lView: LView,
createOpCodes: I18nCreateOpCodes, text: string, isICU: boolean): TNode { createOpCodes: I18nCreateOpCodes, text: string|null, isICU: boolean): TNode {
const i18nNodeIdx = allocExpando(tView, lView, 1); const i18nNodeIdx = allocExpando(tView, lView, 1);
let opCode = i18nNodeIdx << I18nCreateOpCode.SHIFT; let opCode = i18nNodeIdx << I18nCreateOpCode.SHIFT;
let parentTNode = getCurrentParentTNode(); let parentTNode = getCurrentParentTNode();
@ -174,9 +174,12 @@ function createTNodeAndAddOpCode(
opCode |= I18nCreateOpCode.COMMENT; opCode |= I18nCreateOpCode.COMMENT;
ensureIcuContainerVisitorLoaded(loadIcuContainerVisitor); ensureIcuContainerVisitorLoaded(loadIcuContainerVisitor);
} }
createOpCodes.push(opCode, text); createOpCodes.push(opCode, text === null ? '' : text);
// We store `{{?}}` so that when looking at debug `TNodeType.template` we can see where the
// bindings are.
const tNode = createTNodeAtIndex( const tNode = createTNodeAtIndex(
tView, i18nNodeIdx, isICU ? TNodeType.IcuContainer : TNodeType.Element, null, null); tView, i18nNodeIdx, isICU ? TNodeType.Icu : TNodeType.Text,
text === null ? (ngDevMode ? '{{?}}' : '') : text, null);
addTNodeAndUpdateInsertBeforeIndex(existingTNodes, tNode); addTNodeAndUpdateInsertBeforeIndex(existingTNodes, tNode);
const tNodeIdx = tNode.index; const tNodeIdx = tNode.index;
setCurrentTNode(tNode, false /* Text nodes are self closing */); setCurrentTNode(tNode, false /* Text nodes are self closing */);
@ -212,7 +215,7 @@ function i18nStartFirstCreatePassProcessTextNode(
updateOpCodes: I18nUpdateOpCodes, lView: LView, text: string): void { updateOpCodes: I18nUpdateOpCodes, lView: LView, text: string): void {
const hasBinding = text.match(BINDING_REGEXP); const hasBinding = text.match(BINDING_REGEXP);
const tNode = createTNodeAndAddOpCode( const tNode = createTNodeAndAddOpCode(
tView, rootTNode, existingTNodes, lView, createOpCodes, hasBinding ? '' : text, false); tView, rootTNode, existingTNodes, lView, createOpCodes, hasBinding ? null : text, false);
if (hasBinding) { if (hasBinding) {
generateBindingUpdateOpCodes(updateOpCodes, text, tNode.index); generateBindingUpdateOpCodes(updateOpCodes, text, tNode.index);
} }
@ -251,7 +254,7 @@ export function i18nAttributesFirstPass(
const tNode = getTNode(tView, previousElementIndex - HEADER_OFFSET); const tNode = getTNode(tView, previousElementIndex - HEADER_OFFSET);
// Set attributes for Elements only, for other types (like ElementContainer), // Set attributes for Elements only, for other types (like ElementContainer),
// only set inputs below // only set inputs below
if (tNode.type === TNodeType.Element) { if (tNode.type & TNodeType.AnyRNode) {
elementAttributeInternal(tNode, lView, attrName, value, null, null); elementAttributeInternal(tNode, lView, attrName, value, null, null);
} }
// Check if that attribute is a directive input // Check if that attribute is a directive input

View File

@ -12,7 +12,7 @@ import {createTNodeAtIndex} from '../instructions/shared';
import {TIcu} from '../interfaces/i18n'; import {TIcu} from '../interfaces/i18n';
import {TIcuContainerNode, TNode, TNodeType} from '../interfaces/node'; import {TIcuContainerNode, TNode, TNodeType} from '../interfaces/node';
import {TView} from '../interfaces/view'; import {TView} from '../interfaces/view';
import {assertNodeType} from '../node_assert'; import {assertTNodeType} from '../node_assert';
import {addTNodeAndUpdateInsertBeforeIndex} from './i18n_insert_before_index'; import {addTNodeAndUpdateInsertBeforeIndex} from './i18n_insert_before_index';
@ -69,7 +69,7 @@ export function setTIcu(tView: TView, index: number, tIcu: TIcu): void {
if (tNode === null) { if (tNode === null) {
tView.data[index] = tIcu; tView.data[index] = tIcu;
} else { } else {
ngDevMode && assertNodeType(tNode, TNodeType.IcuContainer); ngDevMode && assertTNodeType(tNode, TNodeType.Icu);
tNode.value = tIcu; tNode.value = tIcu;
} }
} }

View File

@ -14,7 +14,7 @@ import {hasClassInput, hasStyleInput, TAttributes, TElementNode, TNode, TNodeFla
import {RElement} from '../interfaces/renderer'; import {RElement} from '../interfaces/renderer';
import {isContentQueryHost, isDirectiveHost} from '../interfaces/type_checks'; import {isContentQueryHost, isDirectiveHost} from '../interfaces/type_checks';
import {HEADER_OFFSET, LView, RENDERER, TView} from '../interfaces/view'; import {HEADER_OFFSET, LView, RENDERER, TView} from '../interfaces/view';
import {assertNodeType} from '../node_assert'; import {assertTNodeType} from '../node_assert';
import {appendChild, createElementNode, writeDirectClass, writeDirectStyle} from '../node_manipulation'; import {appendChild, createElementNode, writeDirectClass, writeDirectStyle} from '../node_manipulation';
import {decreaseElementDepthCount, getBindingIndex, getCurrentTNode, getElementDepthCount, getLView, getNamespace, getTView, increaseElementDepthCount, isCurrentTNodeParent, setCurrentTNode, setCurrentTNodeAsNotParent} from '../state'; import {decreaseElementDepthCount, getBindingIndex, getCurrentTNode, getElementDepthCount, getLView, getNamespace, getTView, increaseElementDepthCount, isCurrentTNodeParent, setCurrentTNode, setCurrentTNodeAsNotParent} from '../state';
import {computeStaticStyling} from '../styling/static_styling'; import {computeStaticStyling} from '../styling/static_styling';
@ -24,6 +24,7 @@ import {setDirectiveInputsWhichShadowsStyling} from './property';
import {createDirectivesInstances, executeContentQueries, getOrCreateTNode, matchingSchemas, resolveDirectives, saveResolvedLocalsInData} from './shared'; import {createDirectivesInstances, executeContentQueries, getOrCreateTNode, matchingSchemas, resolveDirectives, saveResolvedLocalsInData} from './shared';
function elementStartFirstCreatePass( function elementStartFirstCreatePass(
index: number, tView: TView, lView: LView, native: RElement, name: string, index: number, tView: TView, lView: LView, native: RElement, name: string,
attrsIndex?: number|null, localRefsIndex?: number): TElementNode { attrsIndex?: number|null, localRefsIndex?: number): TElementNode {
@ -140,7 +141,7 @@ export function ɵɵelementEnd(): void {
} }
const tNode = currentTNode; const tNode = currentTNode;
ngDevMode && assertNodeType(tNode, TNodeType.Element); ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode);
decreaseElementDepthCount(); decreaseElementDepthCount();

View File

@ -12,7 +12,7 @@ import {registerPostOrderHooks} from '../hooks';
import {TAttributes, TElementContainerNode, TNodeType} from '../interfaces/node'; import {TAttributes, TElementContainerNode, TNodeType} from '../interfaces/node';
import {isContentQueryHost, isDirectiveHost} from '../interfaces/type_checks'; import {isContentQueryHost, isDirectiveHost} from '../interfaces/type_checks';
import {HEADER_OFFSET, LView, RENDERER, T_HOST, TView} from '../interfaces/view'; import {HEADER_OFFSET, LView, RENDERER, T_HOST, TView} from '../interfaces/view';
import {assertNodeType} from '../node_assert'; import {assertTNodeType} from '../node_assert';
import {appendChild} from '../node_manipulation'; import {appendChild} from '../node_manipulation';
import {getBindingIndex, getCurrentTNode, getLView, getTView, isCurrentTNodeParent, setCurrentTNode, setCurrentTNodeAsNotParent} from '../state'; import {getBindingIndex, getCurrentTNode, getLView, getTView, isCurrentTNodeParent, setCurrentTNode, setCurrentTNodeAsNotParent} from '../state';
import {computeStaticStyling} from '../styling/static_styling'; import {computeStaticStyling} from '../styling/static_styling';
@ -108,7 +108,7 @@ export function ɵɵelementContainerEnd(): void {
setCurrentTNode(currentTNode, false); setCurrentTNode(currentTNode, false);
} }
ngDevMode && assertNodeType(currentTNode, TNodeType.ElementContainer); ngDevMode && assertTNodeType(currentTNode, TNodeType.ElementContainer);
if (tView.firstCreatePass) { if (tView.firstCreatePass) {
registerPostOrderHooks(tView, currentTNode); registerPostOrderHooks(tView, currentTNode);

View File

@ -62,7 +62,7 @@ export function ɵɵi18nStart(
const parentRNode = getClosestRElement(tView, sameViewParentTNode, lView); const parentRNode = getClosestRElement(tView, sameViewParentTNode, lView);
// If `parentTNode` is an `ElementContainer` than it has `<!--ng-container--->`. // If `parentTNode` is an `ElementContainer` than it has `<!--ng-container--->`.
// When we do inserts we have to make sure to insert in front of `<!--ng-container--->`. // When we do inserts we have to make sure to insert in front of `<!--ng-container--->`.
const insertInFrontOf = parentTNode && parentTNode.type === TNodeType.ElementContainer ? const insertInFrontOf = parentTNode && (parentTNode.type & TNodeType.ElementContainer) ?
lView[parentTNode.index] : lView[parentTNode.index] :
null; null;
applyCreateOpCodes(lView, tI18n.create, parentRNode, insertInFrontOf); applyCreateOpCodes(lView, tI18n.create, parentRNode, insertInFrontOf);

View File

@ -14,7 +14,7 @@ import {PropertyAliasValue, TNode, TNodeFlags, TNodeType} from '../interfaces/no
import {GlobalTargetResolver, isProceduralRenderer, RElement, Renderer3} from '../interfaces/renderer'; import {GlobalTargetResolver, isProceduralRenderer, RElement, Renderer3} from '../interfaces/renderer';
import {isDirectiveHost} from '../interfaces/type_checks'; import {isDirectiveHost} from '../interfaces/type_checks';
import {CLEANUP, FLAGS, LView, LViewFlags, RENDERER, TView} from '../interfaces/view'; import {CLEANUP, FLAGS, LView, LViewFlags, RENDERER, TView} from '../interfaces/view';
import {assertNodeOfPossibleTypes} from '../node_assert'; import {assertTNodeType} from '../node_assert';
import {getCurrentDirectiveDef, getCurrentTNode, getLView, getTView} from '../state'; import {getCurrentDirectiveDef, getCurrentTNode, getLView, getTView} from '../state';
import {getComponentLViewByIndex, getNativeByTNode, unwrapRNode} from '../util/view_utils'; import {getComponentLViewByIndex, getNativeByTNode, unwrapRNode} from '../util/view_utils';
@ -126,14 +126,12 @@ function listenerInternal(
// register a listener and store its cleanup function on LView. // register a listener and store its cleanup function on LView.
const lCleanup = getLCleanup(lView); const lCleanup = getLCleanup(lView);
ngDevMode && ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode | TNodeType.AnyContainer);
assertNodeOfPossibleTypes(
tNode, [TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer]);
let processOutputs = true; let processOutputs = true;
// add native event listener - applicable to elements only // add native event listener - applicable to elements only
if (tNode.type === TNodeType.Element) { if (tNode.type & TNodeType.AnyRNode) {
const native = getNativeByTNode(tNode, lView) as RElement; const native = getNativeByTNode(tNode, lView) as RElement;
const resolved = eventTargetResolver ? eventTargetResolver(native) : EMPTY_OBJ as any; const resolved = eventTargetResolver ? eventTargetResolver(native) : EMPTY_OBJ as any;
const target = resolved.target || native; const target = resolved.target || native;

View File

@ -17,7 +17,7 @@ import {getInjectorIndex} from '../di';
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container'; import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition'; import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
import {NO_PARENT_INJECTOR, NodeInjectorOffset} from '../interfaces/injector'; import {NO_PARENT_INJECTOR, NodeInjectorOffset} from '../interfaces/injector';
import {AttributeMarker, InsertBeforeIndex, PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TNodeTypeAsString} from '../interfaces/node'; import {AttributeMarker, InsertBeforeIndex, PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, toTNodeTypeAsString} from '../interfaces/node';
import {SelectorFlags} from '../interfaces/projection'; import {SelectorFlags} from '../interfaces/projection';
import {LQueries, TQueries} from '../interfaces/query'; import {LQueries, TQueries} from '../interfaces/query';
import {RComment, RElement, Renderer3, RendererFactory3, RNode} from '../interfaces/renderer'; import {RComment, RElement, Renderer3, RendererFactory3, RNode} from '../interfaces/renderer';
@ -239,7 +239,7 @@ class TNode implements ITNode {
} }
get type_(): string { get type_(): string {
return TNodeTypeAsString[this.type] || `TNodeType.?${this.type}?`; return toTNodeTypeAsString(this.type) || `TNodeType.?${this.type}?`;
} }
get flags_(): string { get flags_(): string {
@ -256,7 +256,7 @@ class TNode implements ITNode {
} }
get template_(): string { get template_(): string {
if (this.value === null && this.type === TNodeType.Element) return '#text'; if (this.type & TNodeType.Text) return this.value!;
const buf: string[] = []; const buf: string[] = [];
const tagName = typeof this.value === 'string' && this.value || this.type_; const tagName = typeof this.value === 'string' && this.value || this.type_;
buf.push('<', tagName); buf.push('<', tagName);
@ -594,7 +594,7 @@ export function buildDebugNode(tNode: ITNode, lView: LView): DebugNode {
} }
return { return {
html: toHtml(native), html: toHtml(native),
type: TNodeTypeAsString[tNode.type], type: toTNodeTypeAsString(tNode.type),
native: native as any, native: native as any,
children: toDebugNodes(tNode.child, lView), children: toDebugNodes(tNode.child, lView),
factories, factories,

View File

@ -5,43 +5,44 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import { Injector } from '../../di'; import {Injector} from '../../di';
import { ErrorHandler } from '../../error_handler'; import {ErrorHandler} from '../../error_handler';
import { DoCheck, OnChanges, OnInit } from '../../interface/lifecycle_hooks'; import {DoCheck, OnChanges, OnInit} from '../../interface/lifecycle_hooks';
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata } from '../../metadata/schema'; import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../metadata/schema';
import { ViewEncapsulation } from '../../metadata/view'; import {ViewEncapsulation} from '../../metadata/view';
import { validateAgainstEventAttributes, validateAgainstEventProperties } from '../../sanitization/sanitization'; import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
import { Sanitizer } from '../../sanitization/sanitizer'; import {Sanitizer} from '../../sanitization/sanitizer';
import { assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertIndexInRange, assertLessThan, assertNotEqual, assertNotSame, assertSame, assertString } from '../../util/assert'; import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertIndexInRange, assertLessThan, assertNotEqual, assertNotSame, assertSame, assertString} from '../../util/assert';
import { createNamedArrayType } from '../../util/named_array_type'; import {createNamedArrayType} from '../../util/named_array_type';
import { initNgDevMode } from '../../util/ng_dev_mode'; import {initNgDevMode} from '../../util/ng_dev_mode';
import { normalizeDebugBindingName, normalizeDebugBindingValue } from '../../util/ng_reflect'; import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
import { stringify } from '../../util/stringify'; import {stringify} from '../../util/stringify';
import { assertFirstCreatePass, assertFirstUpdatePass, assertLContainer, assertLView, assertTNodeForLView, assertTNodeForTView } from '../assert'; import {assertFirstCreatePass, assertFirstUpdatePass, assertLContainer, assertLView, assertTNodeForLView, assertTNodeForTView} from '../assert';
import { attachPatchData } from '../context_discovery'; import {attachPatchData} from '../context_discovery';
import { getFactoryDef } from '../definition'; import {getFactoryDef} from '../definition';
import { diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode } from '../di'; import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di';
import { throwMultipleComponentError } from '../errors'; import {throwMultipleComponentError} from '../errors';
import { executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags } from '../hooks'; import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} from '../hooks';
import { CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS } from '../interfaces/container'; import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS} from '../interfaces/container';
import { ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction } from '../interfaces/definition'; import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import { NodeInjectorFactory, NodeInjectorOffset } from '../interfaces/injector'; import {NodeInjectorFactory, NodeInjectorOffset} from '../interfaces/injector';
import { AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode } from '../interfaces/node'; import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode} from '../interfaces/node';
import { isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText } from '../interfaces/renderer'; import {isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText} from '../interfaces/renderer';
import { SanitizerFn } from '../interfaces/sanitization'; import {SanitizerFn} from '../interfaces/sanitization';
import { isComponentDef, isComponentHost, isContentQueryHost, isRootView } from '../interfaces/type_checks'; import {isComponentDef, isComponentHost, isContentQueryHost, isRootView} from '../interfaces/type_checks';
import { CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType, T_HOST } from '../interfaces/view'; import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view';
import { assertNodeNotOfTypes, assertNodeOfPossibleTypes } from '../node_assert'; import {assertPureTNodeType, assertTNodeType} from '../node_assert';
import { updateTextNode } from '../node_manipulation'; import {updateTextNode} from '../node_manipulation';
import { isInlineTemplate, isNodeMatchingSelectorList } from '../node_selector_matcher'; import {isInlineTemplate, isNodeMatchingSelectorList} from '../node_selector_matcher';
import { enterView, getBindingsEnabled, getCurrentDirectiveIndex, getCurrentParentTNode, getCurrentTNode, getCurrentTNodePlaceholderOk, getSelectedIndex, isCurrentTNodeParent, isInCheckNoChangesMode, isInI18nBlock, leaveView, setBindingIndex, setBindingRootForHostBindings, setCurrentDirectiveIndex, setCurrentQueryIndex, setCurrentTNode, setIsInCheckNoChangesMode, setSelectedIndex } from '../state'; import {enterView, getBindingsEnabled, getCurrentDirectiveIndex, getCurrentParentTNode, getCurrentTNode, getCurrentTNodePlaceholderOk, getSelectedIndex, isCurrentTNodeParent, isInCheckNoChangesMode, isInI18nBlock, leaveView, setBindingIndex, setBindingRootForHostBindings, setCurrentDirectiveIndex, setCurrentQueryIndex, setCurrentTNode, setIsInCheckNoChangesMode, setSelectedIndex} from '../state';
import { NO_CHANGE } from '../tokens'; import {NO_CHANGE} from '../tokens';
import { isAnimationProp, mergeHostAttrs } from '../util/attrs_utils'; import {isAnimationProp, mergeHostAttrs} from '../util/attrs_utils';
import { INTERPOLATION_DELIMITER, renderStringify, stringifyForError } from '../util/misc_utils'; import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
import { getFirstLContainer, getLViewParent, getNextLContainer } from '../util/view_traversal_utils'; import {getFirstLContainer, getLViewParent, getNextLContainer} from '../util/view_traversal_utils';
import { getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapLView, updateTransplantedViewCount, viewAttachedToChangeDetector } from '../util/view_utils'; import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapLView, updateTransplantedViewCount, viewAttachedToChangeDetector} from '../util/view_utils';
import { selectIndexInternal } from './advance';
import { attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData, LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor } from './lview_debug'; import {selectIndexInternal} from './advance';
import {attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData, LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor} from './lview_debug';
@ -196,7 +197,7 @@ export function createLView<T>(
* @param attrs Any attrs for the native element, if applicable * @param attrs Any attrs for the native element, if applicable
*/ */
export function getOrCreateTNode( export function getOrCreateTNode(
tView: TView, index: number, type: TNodeType.Element, name: string|null, tView: TView, index: number, type: TNodeType.Element|TNodeType.Text, name: string|null,
attrs: TAttributes|null): TElementNode; attrs: TAttributes|null): TElementNode;
export function getOrCreateTNode( export function getOrCreateTNode(
tView: TView, index: number, type: TNodeType.Container, name: string|null, tView: TView, index: number, type: TNodeType.Container, name: string|null,
@ -208,12 +209,13 @@ export function getOrCreateTNode(
tView: TView, index: number, type: TNodeType.ElementContainer, name: string|null, tView: TView, index: number, type: TNodeType.ElementContainer, name: string|null,
attrs: TAttributes|null): TElementContainerNode; attrs: TAttributes|null): TElementContainerNode;
export function getOrCreateTNode( export function getOrCreateTNode(
tView: TView, index: number, type: TNodeType.IcuContainer, name: null, tView: TView, index: number, type: TNodeType.Icu, name: null,
attrs: TAttributes|null): TElementContainerNode; attrs: TAttributes|null): TElementContainerNode;
export function getOrCreateTNode( export function getOrCreateTNode(
tView: TView, index: number, type: TNodeType, name: string|null, attrs: TAttributes|null): tView: TView, index: number, type: TNodeType, name: string|null, attrs: TAttributes|null):
TElementNode&TContainerNode&TElementContainerNode&TProjectionNode&TIcuContainerNode { TElementNode&TContainerNode&TElementContainerNode&TProjectionNode&TIcuContainerNode {
// Keep this function short, so that the VM will inline it. // Keep this function short, so that the VM will inline it.
ngDevMode && assertPureTNodeType(type);
const adjustedIndex = index + HEADER_OFFSET; const adjustedIndex = index + HEADER_OFFSET;
let tNode = tView.data[adjustedIndex] as TNode; let tNode = tView.data[adjustedIndex] as TNode;
if (tNode === null) { if (tNode === null) {
@ -225,7 +227,7 @@ export function getOrCreateTNode(
// removed, so we mark it as detached. // removed, so we mark it as detached.
tNode.flags |= TNodeFlags.isDetached; tNode.flags |= TNodeFlags.isDetached;
} }
} else if (tNode.type == TNodeType.Placeholder) { } else if (tNode.type & TNodeType.Placeholder) {
tNode.type = type; tNode.type = type;
tNode.value = name; tNode.value = name;
tNode.attrs = attrs; tNode.attrs = attrs;
@ -818,13 +820,13 @@ export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Container, tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Container,
adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TContainerNode; adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TContainerNode;
export function createTNode( export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Element, tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Element|TNodeType.Text,
adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TElementNode; adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TElementNode;
export function createTNode( export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.ElementContainer, tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.ElementContainer,
adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TElementContainerNode; adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TElementContainerNode;
export function createTNode( export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.IcuContainer, tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Icu,
adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TIcuContainerNode; adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TIcuContainerNode;
export function createTNode( export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Projection, tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Projection,
@ -1012,7 +1014,7 @@ export function elementPropertyInternal<T>(
if (ngDevMode) { if (ngDevMode) {
setNgReflectProperties(lView, element, tNode.type, dataValue, value); setNgReflectProperties(lView, element, tNode.type, dataValue, value);
} }
} else if (tNode.type === TNodeType.Element) { } else if (tNode.type & TNodeType.AnyRNode) {
propName = mapPropName(propName); propName = mapPropName(propName);
if (ngDevMode) { if (ngDevMode) {
@ -1034,7 +1036,7 @@ export function elementPropertyInternal<T>(
(element as RElement).setProperty ? (element as any).setProperty(propName, value) : (element as RElement).setProperty ? (element as any).setProperty(propName, value) :
(element as any)[propName] = value; (element as any)[propName] = value;
} }
} else if (tNode.type === TNodeType.Container || tNode.type === TNodeType.ElementContainer) { } else if (tNode.type & TNodeType.AnyContainer) {
// If the node is a container and the property didn't // If the node is a container and the property didn't
// match any of the inputs or schemas we should throw. // match any of the inputs or schemas we should throw.
if (ngDevMode && !matchingSchemas(tView, tNode.value)) { if (ngDevMode && !matchingSchemas(tView, tNode.value)) {
@ -1057,7 +1059,7 @@ function setNgReflectProperty(
const renderer = lView[RENDERER]; const renderer = lView[RENDERER];
attrName = normalizeDebugBindingName(attrName); attrName = normalizeDebugBindingName(attrName);
const debugValue = normalizeDebugBindingValue(value); const debugValue = normalizeDebugBindingValue(value);
if (type === TNodeType.Element) { if (type & TNodeType.AnyRNode) {
if (value == null) { if (value == null) {
isProceduralRenderer(renderer) ? renderer.removeAttribute((element as RElement), attrName) : isProceduralRenderer(renderer) ? renderer.removeAttribute((element as RElement), attrName) :
(element as RElement).removeAttribute(attrName); (element as RElement).removeAttribute(attrName);
@ -1079,7 +1081,7 @@ function setNgReflectProperty(
export function setNgReflectProperties( export function setNgReflectProperties(
lView: LView, element: RElement|RComment, type: TNodeType, dataValue: PropertyAliasValue, lView: LView, element: RElement|RComment, type: TNodeType, dataValue: PropertyAliasValue,
value: any) { value: any) {
if (type === TNodeType.Element || type === TNodeType.Container) { if (type & (TNodeType.AnyRNode | TNodeType.Container)) {
/** /**
* dataValue is an array containing runtime input or output names for the directives: * dataValue is an array containing runtime input or output names for the directives:
* i+0: directive instance index * i+0: directive instance index
@ -1300,7 +1302,7 @@ function instantiateAllDirectives(
const isComponent = isComponentDef(def); const isComponent = isComponentDef(def);
if (isComponent) { if (isComponent) {
ngDevMode && assertNodeOfPossibleTypes(tNode, [TNodeType.Element]); ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode);
addComponentLogic(lView, tNode as TElementNode, def as ComponentDef<any>); addComponentLogic(lView, tNode as TElementNode, def as ComponentDef<any>);
} }
@ -1386,9 +1388,7 @@ function findDirectiveDefMatches(
tView: TView, viewData: LView, tView: TView, viewData: LView,
tNode: TElementNode|TContainerNode|TElementContainerNode): DirectiveDef<any>[]|null { tNode: TElementNode|TContainerNode|TElementContainerNode): DirectiveDef<any>[]|null {
ngDevMode && assertFirstCreatePass(tView); ngDevMode && assertFirstCreatePass(tView);
ngDevMode && ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode | TNodeType.AnyContainer);
assertNodeOfPossibleTypes(
tNode, [TNodeType.Element, TNodeType.ElementContainer, TNodeType.Container]);
const registry = tView.directiveRegistry; const registry = tView.directiveRegistry;
let matches: any[]|null = null; let matches: any[]|null = null;
@ -1401,8 +1401,8 @@ function findDirectiveDefMatches(
if (isComponentDef(def)) { if (isComponentDef(def)) {
if (ngDevMode) { if (ngDevMode) {
assertNodeOfPossibleTypes( assertTNodeType(
tNode, [TNodeType.Element], tNode, TNodeType.Element,
`"${tNode.value}" tags cannot be used as component hosts. ` + `"${tNode.value}" tags cannot be used as component hosts. ` +
`Please use a different tag to activate the ${stringify(def.type)} component.`); `Please use a different tag to activate the ${stringify(def.type)} component.`);
@ -1518,8 +1518,8 @@ export function elementAttributeInternal(
if (ngDevMode) { if (ngDevMode) {
assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.'); assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.');
validateAgainstEventAttributes(name); validateAgainstEventAttributes(name);
assertNodeNotOfTypes( assertTNodeType(
tNode, [TNodeType.Container, TNodeType.ElementContainer], tNode, TNodeType.Element,
`Attempted to set attribute \`${name}\` on a container node. ` + `Attempted to set attribute \`${name}\` on a container node. ` +
`Host bindings are not valid on ng-container or ng-template.`); `Host bindings are not valid on ng-container or ng-template.`);
} }

View File

@ -705,7 +705,7 @@ function updateStylingMap(
function updateStyling( function updateStyling(
tView: TView, tNode: TNode, lView: LView, renderer: Renderer3, prop: string, tView: TView, tNode: TNode, lView: LView, renderer: Renderer3, prop: string,
value: string|undefined|null|boolean, isClassBased: boolean, bindingIndex: number) { value: string|undefined|null|boolean, isClassBased: boolean, bindingIndex: number) {
if (tNode.type !== TNodeType.Element) { if (!(tNode.type & TNodeType.AnyRNode)) {
// It is possible to have styling on non-elements (such as ng-container). // It is possible to have styling on non-elements (such as ng-container).
// This is rare, but it does happen. In such a case, just ignore the binding. // This is rare, but it does happen. In such a case, just ignore the binding.
return; return;

View File

@ -35,7 +35,7 @@ export function ɵɵtext(index: number, value: string = ''): void {
ngDevMode && assertIndexInRange(lView, adjustedIndex); ngDevMode && assertIndexInRange(lView, adjustedIndex);
const tNode = tView.firstCreatePass ? const tNode = tView.firstCreatePass ?
getOrCreateTNode(tView, index, TNodeType.Element, null, null) : getOrCreateTNode(tView, index, TNodeType.Text, value, null) :
tView.data[adjustedIndex] as TElementNode; tView.data[adjustedIndex] as TElementNode;
const textNative = lView[adjustedIndex] = createTextNode(lView[RENDERER], value); const textNative = lView[adjustedIndex] = createTextNode(lView[RENDERER], value);

View File

@ -15,30 +15,47 @@ import {LView, TView} from './view';
/** /**
* TNodeType corresponds to the {@link TNode} `type` property. * TNodeType corresponds to the {@link TNode} `type` property.
*
* NOTE: type IDs are such that we use each bit to denote a type. This is done so that we can easily
* check if the `TNode` is of more than one type.
*
* `if (tNode.type === TNodeType.Text || tNode.type === TNode.Element)`
* can be written as:
* `if (tNode.type & (TNodeType.Text | TNodeType.Element))`
*
* However any given `TNode` can only be of one type.
*/ */
export const enum TNodeType { export const enum TNodeType {
// FIXME(misko): Add `Text` type so that it would be much easier to reason/debug about `TNode`s. /**
* The TNode contains information about a DOM element aka {@link RText}.
*/
Text = 0b1,
/**
* The TNode contains information about a DOM element aka {@link RElement}.
*/
Element = 0b10,
/** /**
* The TNode contains information about an {@link LContainer} for embedded views. * The TNode contains information about an {@link LContainer} for embedded views.
*/ */
// FIXME(misko): Verify that we still need a `Container`, at the very least update the text. Container = 0b100,
Container = 0,
/**
* The TNode contains information about an `<ng-content>` projection
*/
Projection = 1,
/**
* The TNode contains information about a DOM element aka {@link RNode}.
*/
Element = 2,
/** /**
* The TNode contains information about an `<ng-container>` element {@link RNode}. * The TNode contains information about an `<ng-container>` element {@link RNode}.
*/ */
ElementContainer = 3, ElementContainer = 0b1000,
/**
* The TNode contains information about an `<ng-content>` projection
*/
Projection = 0b10000,
/** /**
* The TNode contains information about an ICU comment used in `i18n`. * The TNode contains information about an ICU comment used in `i18n`.
*/ */
IcuContainer = 4, Icu = 0b100000,
/** /**
* Special node type representing a placeholder for future `TNode` at this location. * Special node type representing a placeholder for future `TNode` at this location.
* *
@ -52,22 +69,31 @@ export const enum TNodeType {
* location. Seeing a `Placeholder` `TNode` already there tells the system that it should reuse * location. Seeing a `Placeholder` `TNode` already there tells the system that it should reuse
* existing `TNode` (rather than create a new one) and just update the missing information. * existing `TNode` (rather than create a new one) and just update the missing information.
*/ */
Placeholder = 5, Placeholder = 0b1000000,
// Combined Types These should never be used for `TNode.type` only as a useful way to check
// if `TNode.type` is one of several choices.
// See: https://github.com/microsoft/TypeScript/issues/35875 why we can't refer to existing enum.
AnyRNode = 0b11, // Text | Element,
AnyContainer = 0b1100, // Container | ElementContainer, // See:
} }
/** /**
* Converts `TNodeType` into human readable text. * Converts `TNodeType` into human readable text.
* Make sure this matches with `TNodeType` * Make sure this matches with `TNodeType`
*/ */
export const TNodeTypeAsString = [ export function toTNodeTypeAsString(tNodeType: TNodeType): string {
'Container', // 0 let text = '';
'Projection', // 1 (tNodeType & TNodeType.Text) && (text += '|Text');
'Element', // 2 (tNodeType & TNodeType.Element) && (text += '|Element');
'ElementContainer', // 3 (tNodeType & TNodeType.Container) && (text += '|Container');
'IcuContainer', // 4 (tNodeType & TNodeType.ElementContainer) && (text += '|ElementContainer');
'Placeholder', // 5 (tNodeType & TNodeType.Projection) && (text += '|Projection');
] as const; (tNodeType & TNodeType.Icu) && (text += '|IcuContainer');
(tNodeType & TNodeType.Placeholder) && (text += '|Placeholder');
return text.length > 0 ? text.substring(1) : text;
}
/** /**
* Corresponds to the TNode.flags property. * Corresponds to the TNode.flags property.
@ -223,7 +249,8 @@ export const enum AttributeMarker {
Template = 4, Template = 4,
/** /**
* Signals that the following attribute is `ngProjectAs` and its value is a parsed `CssSelector`. * Signals that the following attribute is `ngProjectAs` and its value is a parsed
* `CssSelector`.
* *
* For example, given the following HTML: * For example, given the following HTML:
* *
@ -273,10 +300,10 @@ export type TAttributes = (string|AttributeMarker|CssSelector)[];
export type TConstants = (TAttributes|string)[]; export type TConstants = (TAttributes|string)[];
/** /**
* Factory function that returns an array of consts. Consts can be represented as a function in case * Factory function that returns an array of consts. Consts can be represented as a function in
* any additional statements are required to define consts in the list. An example is i18n where * case any additional statements are required to define consts in the list. An example is i18n
* additional i18n calls are generated, which should be executed when consts are requested for the * where additional i18n calls are generated, which should be executed when consts are requested
* first time. * for the first time.
*/ */
export type TConstantsFactory = () => TConstants; export type TConstantsFactory = () => TConstants;
@ -332,8 +359,8 @@ export interface TNode {
* nodes. It can also insert `Hello ` and `!` text node as a child of `<div>`, but it can't * nodes. It can also insert `Hello ` and `!` text node as a child of `<div>`, but it can't
* insert `World` because the `<span>` node has not yet been created. In such a case the * insert `World` because the `<span>` node has not yet been created. In such a case the
* `<span>` `TNode` will have an array which will direct the `<span>` to not only insert * `<span>` `TNode` will have an array which will direct the `<span>` to not only insert
* itself in front of `!` but also to insert the `World` (created by `ɵɵi18nStart`) into `<span>` * itself in front of `!` but also to insert the `World` (created by `ɵɵi18nStart`) into
* itself. * `<span>` itself.
* *
* Pseudo code: * Pseudo code:
* ``` * ```
@ -369,10 +396,11 @@ export interface TNode {
* *
* If the index === -1, there is no injector on this node or any ancestor node in this view. * If the index === -1, there is no injector on this node or any ancestor node in this view.
* *
* If the index !== -1, it is the index of this node's injector OR the index of a parent injector * If the index !== -1, it is the index of this node's injector OR the index of a parent
* in the same view. We pass the parent injector index down the node tree of a view so it's * injector in the same view. We pass the parent injector index down the node tree of a view so
* possible to find the parent injector without walking a potentially deep node tree. Injector * it's possible to find the parent injector without walking a potentially deep node tree.
* indices are not set across view boundaries because there could be multiple component hosts. * Injector indices are not set across view boundaries because there could be multiple component
* hosts.
* *
* If tNode.injectorIndex === tNode.parent.injectorIndex, then the index belongs to a parent * If tNode.injectorIndex === tNode.parent.injectorIndex, then the index belongs to a parent
* injector. * injector.
@ -398,8 +426,8 @@ export interface TNode {
* *
* Valid values are: * Valid values are:
* - `-1` No `hostBindings` instruction has executed. * - `-1` No `hostBindings` instruction has executed.
* - `directiveStart <= directiveStylingLast < directiveEnd`: Points to the `DirectiveDef` of the * - `directiveStart <= directiveStylingLast < directiveEnd`: Points to the `DirectiveDef` of
* last styling instruction which executed in the `hostBindings`. * the last styling instruction which executed in the `hostBindings`.
* *
* This data is needed so that styling instructions know which static styling data needs to be * This data is needed so that styling instructions know which static styling data needs to be
* collected from the `DirectiveDef.hostAttrs`. A styling instruction needs to collect all data * collected from the `DirectiveDef.hostAttrs`. A styling instruction needs to collect all data
@ -408,13 +436,14 @@ export interface TNode {
directiveStylingLast: number; directiveStylingLast: number;
/** /**
* Stores indexes of property bindings. This field is only set in the ngDevMode and holds indexes * Stores indexes of property bindings. This field is only set in the ngDevMode and holds
* of property bindings so TestBed can get bound property metadata for a given node. * indexes of property bindings so TestBed can get bound property metadata for a given node.
*/ */
propertyBindings: number[]|null; propertyBindings: number[]|null;
/** /**
* Stores if Node isComponent, isProjected, hasContentQuery, hasClassInput and hasStyleInput etc. * Stores if Node isComponent, isProjected, hasContentQuery, hasClassInput and hasStyleInput
* etc.
*/ */
flags: TNodeFlags; flags: TNodeFlags;
@ -437,8 +466,8 @@ export interface TNode {
value: any; value: any;
/** /**
* Attributes associated with an element. We need to store attributes to support various use-cases * Attributes associated with an element. We need to store attributes to support various
* (attribute injection, content projection with selectors, directives matching). * use-cases (attribute injection, content projection with selectors, directives matching).
* Attributes are stored statically because reading them from the DOM would be way too slow for * Attributes are stored statically because reading them from the DOM would be way too slow for
* content projection and queries. * content projection and queries.
* *
@ -528,10 +557,10 @@ export interface TNode {
next: TNode|null; next: TNode|null;
/** /**
* The next projected sibling. Since in Angular content projection works on the node-by-node basis * The next projected sibling. Since in Angular content projection works on the node-by-node
* the act of projecting nodes might change nodes relationship at the insertion point (target * basis the act of projecting nodes might change nodes relationship at the insertion point
* view). At the same time we need to keep initial relationship between nodes as expressed in * (target view). At the same time we need to keep initial relationship between nodes as
* content view. * expressed in content view.
*/ */
projectionNext: TNode|null; projectionNext: TNode|null;
@ -584,8 +613,8 @@ export interface TNode {
* - `projection` size is equal to the number of projections `<ng-content>`. The size of * - `projection` size is equal to the number of projections `<ng-content>`. The size of
* `c1` will be `1` because `<child>` has only one `<ng-content>`. * `c1` will be `1` because `<child>` has only one `<ng-content>`.
* - we store `projection` with the host (`c1`, `c2`) rather than the `<ng-content>` (`cont1`) * - we store `projection` with the host (`c1`, `c2`) rather than the `<ng-content>` (`cont1`)
* because the same component (`<child>`) can be used in multiple locations (`c1`, `c2`) and as * because the same component (`<child>`) can be used in multiple locations (`c1`, `c2`) and
* a result have different set of nodes to project. * as a result have different set of nodes to project.
* - without `projection` it would be difficult to efficiently traverse nodes to be projected. * - without `projection` it would be difficult to efficiently traverse nodes to be projected.
* *
* If `typeof projection == 'number'` then `TNode` is a `<ng-content>` element: * If `typeof projection == 'number'` then `TNode` is a `<ng-content>` element:
@ -619,9 +648,9 @@ export interface TNode {
* (e.g. `<div style="width:200px;">`) * (e.g. `<div style="width:200px;">`)
* Must be stored separately from `tNode.styles` to facilitate setting directive * Must be stored separately from `tNode.styles` to facilitate setting directive
* inputs that shadow the `style` property. If we used `tNode.styles` as is for shadowed inputs, * inputs that shadow the `style` property. If we used `tNode.styles` as is for shadowed inputs,
* we would feed host styles back into directives as "inputs". If we used `tNode.attrs`, we would * we would feed host styles back into directives as "inputs". If we used `tNode.attrs`, we
* have to concatenate the attributes on every template pass. Instead, we process once on first * would have to concatenate the attributes on every template pass. Instead, we process once on
* create pass and store here. * first create pass and store here.
*/ */
stylesWithoutHost: string|null; stylesWithoutHost: string|null;
@ -629,8 +658,8 @@ export interface TNode {
* A `KeyValueArray` version of residual `styles`. * A `KeyValueArray` version of residual `styles`.
* *
* When there are styling instructions than each instruction stores the static styling * When there are styling instructions than each instruction stores the static styling
* which is of lower priority than itself. This means that there may be a higher priority styling * which is of lower priority than itself. This means that there may be a higher priority
* than the instruction. * styling than the instruction.
* *
* Imagine: * Imagine:
* ``` * ```
@ -671,10 +700,10 @@ export interface TNode {
* Populated when there are one or more initial classes on an element * Populated when there are one or more initial classes on an element
* (e.g. `<div class="SOME_CLASS">`) * (e.g. `<div class="SOME_CLASS">`)
* Must be stored separately from `tNode.classes` to facilitate setting directive * Must be stored separately from `tNode.classes` to facilitate setting directive
* inputs that shadow the `class` property. If we used `tNode.classes` as is for shadowed inputs, * inputs that shadow the `class` property. If we used `tNode.classes` as is for shadowed
* we would feed host classes back into directives as "inputs". If we used `tNode.attrs`, we would * inputs, we would feed host classes back into directives as "inputs". If we used
* have to concatenate the attributes on every template pass. Instead, we process once on first * `tNode.attrs`, we would have to concatenate the attributes on every template pass. Instead,
* create pass and store here. * we process once on first create pass and store here.
*/ */
classesWithoutHost: string|null; classesWithoutHost: string|null;
@ -740,8 +769,8 @@ export interface TElementNode extends TNode {
/** /**
* If this is a component TNode with projection, this will be an array of projected * If this is a component TNode with projection, this will be an array of projected
* TNodes or native nodes (see TNode.projection for more info). If it's a regular element node or * TNodes or native nodes (see TNode.projection for more info). If it's a regular element node
* a component without projection, it will be null. * or a component without projection, it will be null.
*/ */
projection: (TNode|RNode[])[]|null; projection: (TNode|RNode[])[]|null;

View File

@ -15,7 +15,7 @@ import {Sanitizer} from '../../sanitization/sanitizer';
import {LContainer} from './container'; import {LContainer} from './container';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList, ViewQueriesFunction} from './definition'; import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList, ViewQueriesFunction} from './definition';
import {I18nUpdateOpCodes, TI18n, TIcu} from './i18n'; import {I18nUpdateOpCodes, TI18n, TIcu} from './i18n';
import {TConstants, TNode, TNodeTypeAsString} from './node'; import {TConstants, TNode} from './node';
import {PlayerHandler} from './player'; import {PlayerHandler} from './player';
import {LQueries, TQueries} from './query'; import {LQueries, TQueries} from './query';
import {RComment, RElement, Renderer3, RendererFactory3} from './renderer'; import {RComment, RElement, Renderer3, RendererFactory3} from './renderer';
@ -1025,7 +1025,7 @@ export interface DebugNode {
/** /**
* Human readable node type. * Human readable node type.
*/ */
type: typeof TNodeTypeAsString[number]; type: string;
/** /**
* DOM native node. * DOM native node.

View File

@ -6,44 +6,29 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {assertDefined, assertEqual} from '../util/assert'; import {assertDefined, throwError} from '../util/assert';
import {TNode, TNodeType, toTNodeTypeAsString} from './interfaces/node';
import {TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeType, TNodeTypeAsString, TProjectionNode} from './interfaces/node'; export function assertTNodeType(
tNode: TNode|null, expectedTypes: TNodeType, message?: string): void {
export function assertNodeType(
tNode: TNode, type: TNodeType.Container): asserts tNode is TContainerNode;
export function assertNodeType(
tNode: TNode, type: TNodeType.Element): asserts tNode is TElementNode;
export function assertNodeType(
tNode: TNode, type: TNodeType.ElementContainer): asserts tNode is TElementContainerNode;
export function assertNodeType(
tNode: TNode, type: TNodeType.IcuContainer): asserts tNode is TIcuContainerNode;
export function assertNodeType(
tNode: TNode, type: TNodeType.Projection): asserts tNode is TProjectionNode;
export function assertNodeType(tNode: TNode, type: TNodeType): asserts tNode is TNode {
assertDefined(tNode, 'should be called with a TNode'); assertDefined(tNode, 'should be called with a TNode');
assertEqual(tNode.type, type, `should be a ${typeName(type)}`); if ((tNode.type & expectedTypes) === 0) {
throwError(
message ||
`Expected [${toTNodeTypeAsString(expectedTypes)}] but got ${
toTNodeTypeAsString(tNode.type)}.`);
}
} }
export function assertNodeOfPossibleTypes( export function assertPureTNodeType(type: TNodeType) {
tNode: TNode|null, types: TNodeType[], message?: string): void { if (!(type === TNodeType.Element || //
assertDefined(tNode, 'should be called with a TNode'); type === TNodeType.Text || //
const found = types.some(type => tNode.type === type); type === TNodeType.Container || //
assertEqual( type === TNodeType.ElementContainer || //
found, true, type === TNodeType.Icu || //
message ?? type === TNodeType.Projection || //
`Should be one of ${types.map(typeName).join(', ')} but got ${typeName(tNode.type)}`); type === TNodeType.Placeholder)) {
} throwError(`Expected TNodeType to have only a single type selected, but got ${
toTNodeTypeAsString(type)}.`);
export function assertNodeNotOfTypes(tNode: TNode, types: TNodeType[], message?: string): void { }
assertDefined(tNode, 'should be called with a TNode');
const found = types.some(type => tNode.type === type);
assertEqual(
found, false,
message ??
`Should not be one of ${types.map(typeName).join(', ')} but got ${typeName(tNode.type)}`);
}
function typeName(type: TNodeType): string {
return TNodeTypeAsString[type] || '<unknown>';
} }

View File

@ -22,10 +22,12 @@ import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'
import {isProceduralRenderer, ProceduralRenderer3, RComment, RElement, Renderer3, RNode, RText, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; import {isProceduralRenderer, ProceduralRenderer3, RComment, RElement, Renderer3, RNode, RText, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
import {isLContainer, isLView} from './interfaces/type_checks'; import {isLContainer, isLView} from './interfaces/type_checks';
import {CHILD_HEAD, CLEANUP, DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, DestroyHookData, FLAGS, HookData, HookFn, HOST, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, T_HOST, TVIEW, TView, TViewType, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; import {CHILD_HEAD, CLEANUP, DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, DestroyHookData, FLAGS, HookData, HookFn, HOST, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, T_HOST, TVIEW, TView, TViewType, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {assertTNodeType} from './node_assert';
import {getLViewParent} from './util/view_traversal_utils'; import {getLViewParent} from './util/view_traversal_utils';
import {getNativeByTNode, unwrapRNode, updateTransplantedViewCount} from './util/view_utils'; import {getNativeByTNode, unwrapRNode, updateTransplantedViewCount} from './util/view_utils';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5; const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
const enum WalkTNodeTreeAction { const enum WalkTNodeTreeAction {
@ -539,9 +541,8 @@ export function getClosestRElement(tView: TView, tNode: TNode|null, lView: LView
let parentTNode: TNode|null = tNode; let parentTNode: TNode|null = tNode;
// Skip over element and ICU containers as those are represented by a comment node and // Skip over element and ICU containers as those are represented by a comment node and
// can't be used as a render parent. // can't be used as a render parent.
while (parentTNode != null && while (parentTNode !== null &&
(parentTNode.type === TNodeType.ElementContainer || (parentTNode.type & (TNodeType.ElementContainer | TNodeType.Icu))) {
parentTNode.type === TNodeType.IcuContainer)) {
tNode = parentTNode; tNode = parentTNode;
parentTNode = tNode.parent; parentTNode = tNode.parent;
} }
@ -553,7 +554,7 @@ export function getClosestRElement(tView: TView, tNode: TNode|null, lView: LView
// it should always be eager. // it should always be eager.
return lView[HOST]; return lView[HOST];
} else { } else {
// ngDevMode && assertTNodeType(parentTNode, TNodeType.AnyRNode | TNodeType.Container); ngDevMode && assertTNodeType(parentTNode, TNodeType.AnyRNode | TNodeType.Container);
if (parentTNode.flags & TNodeFlags.isComponentHost) { if (parentTNode.flags & TNodeFlags.isComponentHost) {
ngDevMode && assertTNodeForLView(parentTNode, lView); ngDevMode && assertTNodeForLView(parentTNode, lView);
const tData = tView.data; const tData = tView.data;
@ -650,8 +651,7 @@ function getInsertInFrontOfRNode(parentTNode: TNode, currentTNode: TNode, lView:
const insertBeforeIndex = const insertBeforeIndex =
Array.isArray(tNodeInsertBeforeIndex) ? tNodeInsertBeforeIndex[0] : tNodeInsertBeforeIndex; Array.isArray(tNodeInsertBeforeIndex) ? tNodeInsertBeforeIndex[0] : tNodeInsertBeforeIndex;
if (insertBeforeIndex === null) { if (insertBeforeIndex === null) {
if (parentTNode.type === TNodeType.ElementContainer || if (parentTNode.type & (TNodeType.ElementContainer | TNodeType.Icu)) {
parentTNode.type === TNodeType.IcuContainer) {
return getNativeByTNode(parentTNode, lView); return getNativeByTNode(parentTNode, lView);
} }
} else { } else {
@ -716,7 +716,7 @@ function processI18nText(
const isProcedural = isProceduralRenderer(renderer); const isProcedural = isProceduralRenderer(renderer);
let i18nParent: RElement|null = childRNode as RElement; let i18nParent: RElement|null = childRNode as RElement;
let anchorRNode: RNode|null = null; let anchorRNode: RNode|null = null;
if (childTNode.type !== TNodeType.Element) { if (!(childTNode.type & TNodeType.AnyRNode)) {
anchorRNode = i18nParent; anchorRNode = i18nParent;
i18nParent = parentRElement; i18nParent = parentRElement;
} }
@ -738,17 +738,17 @@ function processI18nText(
*/ */
function getFirstNativeNode(lView: LView, tNode: TNode|null): RNode|null { function getFirstNativeNode(lView: LView, tNode: TNode|null): RNode|null {
if (tNode !== null) { if (tNode !== null) {
ngDevMode && assertNodeOfPossibleTypes(tNode, [ ngDevMode &&
TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer, TNodeType.IcuContainer, assertTNodeType(
TNodeType.Projection tNode,
]); TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Icu | TNodeType.Projection);
const tNodeType = tNode.type; const tNodeType = tNode.type;
if (tNodeType === TNodeType.Element) { if (tNodeType & TNodeType.AnyRNode) {
return getNativeByTNode(tNode, lView); return getNativeByTNode(tNode, lView);
} else if (tNodeType === TNodeType.Container) { } else if (tNodeType & TNodeType.Container) {
return getBeforeNodeForView(-1, lView[tNode.index]); return getBeforeNodeForView(-1, lView[tNode.index]);
} else if (tNodeType === TNodeType.ElementContainer) { } else if (tNodeType & TNodeType.ElementContainer) {
const elIcuContainerChild = tNode.child; const elIcuContainerChild = tNode.child;
if (elIcuContainerChild !== null) { if (elIcuContainerChild !== null) {
return getFirstNativeNode(lView, elIcuContainerChild); return getFirstNativeNode(lView, elIcuContainerChild);
@ -760,7 +760,7 @@ function getFirstNativeNode(lView: LView, tNode: TNode|null): RNode|null {
return unwrapRNode(rNodeOrLContainer); return unwrapRNode(rNodeOrLContainer);
} }
} }
} else if (tNodeType === TNodeType.IcuContainer) { } else if (tNodeType & TNodeType.Icu) {
let nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView); let nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView);
let rNode: RNode|null = nextRNode(); let rNode: RNode|null = nextRNode();
// If the ICU container has no nodes, than we use the ICU anchor as the node. // If the ICU container has no nodes, than we use the ICU anchor as the node.
@ -824,10 +824,10 @@ function applyNodes(
parentRElement: RElement|null, beforeNode: RNode|null, isProjection: boolean) { parentRElement: RElement|null, beforeNode: RNode|null, isProjection: boolean) {
while (tNode != null) { while (tNode != null) {
ngDevMode && assertTNodeForLView(tNode, lView); ngDevMode && assertTNodeForLView(tNode, lView);
ngDevMode && assertNodeOfPossibleTypes(tNode, [ ngDevMode &&
TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer, TNodeType.Projection, assertTNodeType(
TNodeType.IcuContainer tNode,
]); TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Projection | TNodeType.Icu);
const rawSlotValue = lView[tNode.index]; const rawSlotValue = lView[tNode.index];
const tNodeType = tNode.type; const tNodeType = tNode.type;
if (isProjection) { if (isProjection) {
@ -837,21 +837,21 @@ function applyNodes(
} }
} }
if ((tNode.flags & TNodeFlags.isDetached) !== TNodeFlags.isDetached) { if ((tNode.flags & TNodeFlags.isDetached) !== TNodeFlags.isDetached) {
if (tNodeType === TNodeType.ElementContainer) { if (tNodeType & TNodeType.ElementContainer) {
applyNodes(renderer, action, tNode.child, lView, parentRElement, beforeNode, false); applyNodes(renderer, action, tNode.child, lView, parentRElement, beforeNode, false);
applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode); applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode);
} else if (tNodeType === TNodeType.IcuContainer) { } else if (tNodeType & TNodeType.Icu) {
const nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView); const nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView);
let rNode: RNode|null; let rNode: RNode|null;
while (rNode = nextRNode()) { while (rNode = nextRNode()) {
applyToElementOrContainer(action, renderer, parentRElement, rNode, beforeNode); applyToElementOrContainer(action, renderer, parentRElement, rNode, beforeNode);
} }
applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode); applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode);
} else if (tNodeType === TNodeType.Projection) { } else if (tNodeType & TNodeType.Projection) {
applyProjectionRecursive( applyProjectionRecursive(
renderer, action, lView, tNode as TProjectionNode, parentRElement, beforeNode); renderer, action, lView, tNode as TProjectionNode, parentRElement, beforeNode);
} else { } else {
ngDevMode && assertNodeOfPossibleTypes(tNode, [TNodeType.Element, TNodeType.Container]); ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode | TNodeType.Container);
applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode); applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode);
} }
} }

View File

@ -27,7 +27,7 @@ import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node'; import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
import {LQueries, LQuery, TQueries, TQuery, TQueryMetadata, unusedValueExportToPlacateAjd as unused4} from './interfaces/query'; import {LQueries, LQuery, TQueries, TQuery, TQueryMetadata, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
import {DECLARATION_LCONTAINER, LView, PARENT, QUERIES, TVIEW, TView} from './interfaces/view'; import {DECLARATION_LCONTAINER, LView, PARENT, QUERIES, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes} from './node_assert'; import {assertTNodeType} from './node_assert';
import {getCurrentQueryIndex, getCurrentTNode, getLView, getTView, setCurrentQueryIndex} from './state'; import {getCurrentQueryIndex, getCurrentTNode, getLView, getTView, setCurrentQueryIndex} from './state';
import {isCreationMode} from './util/view_utils'; import {isCreationMode} from './util/view_utils';
import {createContainerRef, createElementRef, createTemplateRef} from './view_engine_compatibility'; import {createContainerRef, createElementRef, createTemplateRef} from './view_engine_compatibility';
@ -217,7 +217,7 @@ class TQuery_ implements TQuery {
// - <needs-target><ng-container><i #target></i></ng-container></needs-target>: here we need // - <needs-target><ng-container><i #target></i></ng-container></needs-target>: here we need
// to go past `<ng-container>` to determine <i #target> parent node (but we shouldn't traverse // to go past `<ng-container>` to determine <i #target> parent node (but we shouldn't traverse
// up past the query's host node!). // up past the query's host node!).
while (parent !== null && parent.type === TNodeType.ElementContainer && while (parent !== null && (parent.type & TNodeType.ElementContainer) &&
parent.index !== declarationNodeIdx) { parent.index !== declarationNodeIdx) {
parent = parent.parent; parent = parent.parent;
} }
@ -238,7 +238,7 @@ class TQuery_ implements TQuery {
} }
} else { } else {
if ((predicate as any) === ViewEngine_TemplateRef) { if ((predicate as any) === ViewEngine_TemplateRef) {
if (tNode.type === TNodeType.Container) { if (tNode.type & TNodeType.Container) {
this.matchTNodeWithReadOption(tView, tNode, -1); this.matchTNodeWithReadOption(tView, tNode, -1);
} }
} else { } else {
@ -253,7 +253,7 @@ class TQuery_ implements TQuery {
const read = this.metadata.read; const read = this.metadata.read;
if (read !== null) { if (read !== null) {
if (read === ViewEngine_ElementRef || read === ViewContainerRef || if (read === ViewEngine_ElementRef || read === ViewContainerRef ||
read === ViewEngine_TemplateRef && tNode.type === TNodeType.Container) { read === ViewEngine_TemplateRef && (tNode.type & TNodeType.Container)) {
this.addMatch(tNode.index, -2); this.addMatch(tNode.index, -2);
} else { } else {
const directiveOrProviderIdx = const directiveOrProviderIdx =
@ -299,9 +299,9 @@ function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null {
function createResultByTNodeType(tNode: TNode, currentView: LView): any { function createResultByTNodeType(tNode: TNode, currentView: LView): any {
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) { if (tNode.type & (TNodeType.AnyRNode | TNodeType.ElementContainer)) {
return createElementRef(ViewEngine_ElementRef, tNode, currentView); return createElementRef(ViewEngine_ElementRef, tNode, currentView);
} else if (tNode.type === TNodeType.Container) { } else if (tNode.type & TNodeType.Container) {
return createTemplateRef(ViewEngine_TemplateRef, ViewEngine_ElementRef, tNode, currentView); return createTemplateRef(ViewEngine_TemplateRef, ViewEngine_ElementRef, tNode, currentView);
} }
return null; return null;
@ -327,9 +327,7 @@ function createSpecialToken(lView: LView, tNode: TNode, read: any): any {
} else if (read === ViewEngine_TemplateRef) { } else if (read === ViewEngine_TemplateRef) {
return createTemplateRef(ViewEngine_TemplateRef, ViewEngine_ElementRef, tNode, lView); return createTemplateRef(ViewEngine_TemplateRef, ViewEngine_ElementRef, tNode, lView);
} else if (read === ViewContainerRef) { } else if (read === ViewContainerRef) {
ngDevMode && ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode | TNodeType.AnyContainer);
assertNodeOfPossibleTypes(
tNode, [TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer]);
return createContainerRef( return createContainerRef(
ViewContainerRef, ViewEngine_ElementRef, ViewContainerRef, ViewEngine_ElementRef,
tNode as TElementNode | TContainerNode | TElementContainerNode, lView); tNode as TElementNode | TContainerNode | TElementContainerNode, lView);

View File

@ -12,6 +12,7 @@ import {DirectiveDef} from './interfaces/definition';
import {TNode, TNodeType} from './interfaces/node'; import {TNode, TNodeType} from './interfaces/node';
import {CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState, TData, TVIEW, TView} from './interfaces/view'; import {CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState, TData, TVIEW, TView} from './interfaces/view';
import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces'; import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces';
import {assertTNodeType} from './node_assert';
import {getTNode} from './util/view_utils'; import {getTNode} from './util/view_utils';

View File

@ -27,7 +27,7 @@ import {TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode,
import {isProceduralRenderer, RComment, RElement} from './interfaces/renderer'; import {isProceduralRenderer, RComment, RElement} from './interfaces/renderer';
import {isComponentHost, isLContainer, isLView, isRootView} from './interfaces/type_checks'; import {isComponentHost, isLContainer, isLView, isRootView} from './interfaces/type_checks';
import {DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, LView, LViewFlags, PARENT, QUERIES, RENDERER, T_HOST, TVIEW, TView} from './interfaces/view'; import {DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, LView, LViewFlags, PARENT, QUERIES, RENDERER, T_HOST, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes} from './node_assert'; import {assertTNodeType} from './node_assert';
import {addViewToContainer, appendChild, destroyLView, detachView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode} from './node_manipulation'; import {addViewToContainer, appendChild, destroyLView, detachView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode} from './node_manipulation';
import {getCurrentTNode, getLView} from './state'; import {getCurrentTNode, getLView} from './state';
import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from './util/injector_utils'; import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from './util/injector_utils';
@ -123,7 +123,7 @@ export function createTemplateRef<T>(
}; };
} }
if (hostTNode.type === TNodeType.Container) { if (hostTNode.type & TNodeType.Container) {
ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated'); ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated');
return new R3TemplateRef( return new R3TemplateRef(
hostView, hostTNode as TContainerNode, hostView, hostTNode as TContainerNode,
@ -357,9 +357,7 @@ export function createContainerRef(
}; };
} }
ngDevMode && ngDevMode && assertTNodeType(hostTNode, TNodeType.AnyContainer | TNodeType.AnyRNode);
assertNodeOfPossibleTypes(
hostTNode, [TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer]);
let lContainer: LContainer; let lContainer: LContainer;
const slotValue = hostView[hostTNode.index]; const slotValue = hostView[hostTNode.index];
@ -372,7 +370,7 @@ export function createContainerRef(
// comment and we can reuse that comment as anchor element for the new LContainer. // comment and we can reuse that comment as anchor element for the new LContainer.
// The comment node in question is already part of the DOM structure so we don't need to append // The comment node in question is already part of the DOM structure so we don't need to append
// it again. // it again.
if (hostTNode.type === TNodeType.ElementContainer) { if (hostTNode.type & TNodeType.ElementContainer) {
commentNode = unwrapRNode(slotValue) as RComment; commentNode = unwrapRNode(slotValue) as RComment;
} else { } else {
ngDevMode && ngDevMode.rendererCreateComment++; ngDevMode && ngDevMode.rendererCreateComment++;
@ -431,9 +429,7 @@ function createViewRef(tNode: TNode, lView: LView, isPipe: boolean): ViewEngine_
// Instead we want the LView for the component View and so we need to look it up. // Instead we want the LView for the component View and so we need to look it up.
const componentView = getComponentLViewByIndex(tNode.index, lView); // look down const componentView = getComponentLViewByIndex(tNode.index, lView); // look down
return new ViewRef(componentView, componentView); return new ViewRef(componentView, componentView);
} else if ( } else if (tNode.type & (TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Icu)) {
tNode.type === TNodeType.Element || tNode.type === TNodeType.Container ||
tNode.type === TNodeType.ElementContainer || tNode.type === TNodeType.IcuContainer) {
// The LView represents the location where the injection is requested from. // The LView represents the location where the injection is requested from.
// We need to locate the containing LView (in case where the `lView` is an embedded view) // We need to locate the containing LView (in case where the `lView` is an embedded view)
const hostComponentView = lView[DECLARATION_COMPONENT_VIEW]; // look up const hostComponentView = lView[DECLARATION_COMPONENT_VIEW]; // look up

View File

@ -19,7 +19,7 @@ import {TElementNode, TIcuContainerNode, TNode, TNodeType} from './interfaces/no
import {RNode} from './interfaces/renderer'; import {RNode} from './interfaces/renderer';
import {isLContainer} from './interfaces/type_checks'; import {isLContainer} from './interfaces/type_checks';
import {CONTEXT, DECLARATION_COMPONENT_VIEW, FLAGS, LView, LViewFlags, T_HOST, TVIEW, TView} from './interfaces/view'; import {CONTEXT, DECLARATION_COMPONENT_VIEW, FLAGS, LView, LViewFlags, T_HOST, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes} from './node_assert'; import {assertTNodeType} from './node_assert';
import {destroyLView, renderDetachView} from './node_manipulation'; import {destroyLView, renderDetachView} from './node_manipulation';
import {getLViewParent} from './util/view_traversal_utils'; import {getLViewParent} from './util/view_traversal_utils';
import {unwrapRNode} from './util/view_utils'; import {unwrapRNode} from './util/view_utils';
@ -324,10 +324,10 @@ function collectNativeNodes(
tView: TView, lView: LView, tNode: TNode|null, result: any[], tView: TView, lView: LView, tNode: TNode|null, result: any[],
isProjection: boolean = false): any[] { isProjection: boolean = false): any[] {
while (tNode !== null) { while (tNode !== null) {
ngDevMode && assertNodeOfPossibleTypes(tNode, [ ngDevMode &&
TNodeType.Element, TNodeType.Container, TNodeType.Projection, TNodeType.ElementContainer, assertTNodeType(
TNodeType.IcuContainer tNode,
]); TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Projection | TNodeType.Icu);
const lNode = lView[tNode.index]; const lNode = lView[tNode.index];
if (lNode !== null) { if (lNode !== null) {
@ -349,15 +349,15 @@ function collectNativeNodes(
} }
const tNodeType = tNode.type; const tNodeType = tNode.type;
if (tNodeType === TNodeType.ElementContainer) { if (tNodeType & TNodeType.ElementContainer) {
collectNativeNodes(tView, lView, tNode.child, result); collectNativeNodes(tView, lView, tNode.child, result);
} else if (tNodeType === TNodeType.IcuContainer) { } else if (tNodeType & TNodeType.Icu) {
const nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView); const nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView);
let rNode: RNode|null; let rNode: RNode|null;
while (rNode = nextRNode()) { while (rNode = nextRNode()) {
result.push(rNode); result.push(rNode);
} }
} else if (tNodeType === TNodeType.Projection) { } else if (tNodeType & TNodeType.Projection) {
const componentView = lView[DECLARATION_COMPONENT_VIEW]; const componentView = lView[DECLARATION_COMPONENT_VIEW];
const componentHost = componentView[T_HOST] as TElementNode; const componentHost = componentView[T_HOST] as TElementNode;
const slotIdx = tNode.projection as number; const slotIdx = tNode.projection as number;

View File

@ -70,7 +70,7 @@ onlyInIvy('Ivy specific').describe('Debug Representation', () => {
length: 1, length: 1,
content: [{ content: [{
index: HEADER_OFFSET + 3, index: HEADER_OFFSET + 3,
t: matchTNode({type: TNodeType.Element, value: null}), t: matchTNode({type: TNodeType.Text, value: '{{?}}'}),
l: matchDomText('Hello World') l: matchDomText('Hello World')
}] }]
}); });

View File

@ -618,7 +618,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
const exclamation = b.nextSibling!; const exclamation = b.nextSibling!;
const lViewDebug = lView.debug!; const lViewDebug = lView.debug!;
expect(lViewDebug.nodes.map(toTypeContent)).toEqual([ expect(lViewDebug.nodes.map(toTypeContent)).toEqual([
'Element(Hello )', 'Element(<b>)', 'Element(!)' 'Text(Hello )', 'Element(<b>)', 'Text(!)'
]); ]);
expect(lViewDebug.decls).toEqual({ expect(lViewDebug.decls).toEqual({
start: HEADER_OFFSET, start: HEADER_OFFSET,

View File

@ -668,7 +668,7 @@ describe('Runtime i18n', () => {
i18nRangeOffsetOpcode(0), 'Hello World!', // i18nRangeOffsetOpcode(0), 'Hello World!', //
]); ]);
const lViewDebug = fixture.lView.debug!; const lViewDebug = fixture.lView.debug!;
expect(lViewDebug.template).toEqual('<div>#text</div>'); expect(lViewDebug.template).toEqual('<div>Hello World!</div>');
}); });
it('should process text with a child node', () => { it('should process text with a child node', () => {
@ -685,7 +685,7 @@ describe('Runtime i18n', () => {
insertBeforeIndex: i18nRangeOffset(1), insertBeforeIndex: i18nRangeOffset(1),
})); }));
const lViewDebug = fixture.lView.debug!; const lViewDebug = fixture.lView.debug!;
expect(lViewDebug.template).toEqual('<div>#text<Placeholder></Placeholder>#text</div>'); expect(lViewDebug.template).toEqual('<div>Hello <Placeholder></Placeholder>!</div>');
}); });
it('should process text with a child node that has text', () => { it('should process text with a child node that has text', () => {
@ -729,7 +729,7 @@ describe('Runtime i18n', () => {
'if (mask & 0b10) { (lView[51] as Text).textContent = `${lView[i-2]}`; }' 'if (mask & 0b10) { (lView[51] as Text).textContent = `${lView[i-2]}`; }'
])); ]));
const lViewDebug = fixture.lView.debug!; const lViewDebug = fixture.lView.debug!;
expect(lViewDebug.template).toEqual('<div>#text<Placeholder>#text</Placeholder>#text</div>'); expect(lViewDebug.template).toEqual('<div>{{?}}<Placeholder>{{?}}</Placeholder>!</div>');
}); });
it('should process text with a child template', () => { it('should process text with a child template', () => {

View File

@ -6,16 +6,23 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {TNodeType, TNodeTypeAsString} from '@angular/core/src/render3/interfaces/node'; import {TNodeType, toTNodeTypeAsString} from '@angular/core/src/render3/interfaces/node';
describe('node interfaces', () => { describe('node interfaces', () => {
describe('TNodeType', () => { describe('TNodeType', () => {
it('should agree with TNodeTypeAsString', () => { it('should agree with toTNodeTypeAsString', () => {
expect(TNodeTypeAsString[TNodeType.Container]).toEqual('Container'); expect(toTNodeTypeAsString(TNodeType.Element)).toEqual('Element');
expect(TNodeTypeAsString[TNodeType.Projection]).toEqual('Projection'); expect(toTNodeTypeAsString(TNodeType.Text)).toEqual('Text');
expect(TNodeTypeAsString[TNodeType.Element]).toEqual('Element'); expect(toTNodeTypeAsString(TNodeType.Container)).toEqual('Container');
expect(TNodeTypeAsString[TNodeType.ElementContainer]).toEqual('ElementContainer'); expect(toTNodeTypeAsString(TNodeType.Projection)).toEqual('Projection');
expect(TNodeTypeAsString[TNodeType.IcuContainer]).toEqual('IcuContainer'); expect(toTNodeTypeAsString(TNodeType.ElementContainer)).toEqual('ElementContainer');
expect(toTNodeTypeAsString(TNodeType.Icu)).toEqual('IcuContainer');
expect(toTNodeTypeAsString(TNodeType.Placeholder)).toEqual('Placeholder');
expect(toTNodeTypeAsString(
TNodeType.Container | TNodeType.Projection | TNodeType.Element |
TNodeType.ElementContainer | TNodeType.Icu))
.toEqual('Element|Container|ElementContainer|Projection|IcuContainer');
}); });
}); });
}); });