fix(core): Store ICU state in LView rather than in TView (#39233)

Before this refactoring/fix the ICU would store the current selected
index in `TView`. This is incorrect, since if ICU is in `ngFor` it will
cause issues in some circumstances. This refactoring properly moves the
state to `LView`.

closes #37021
closes #38144
closes #38073

PR Close #39233
This commit is contained in:
Misko Hevery
2020-10-21 18:33:00 -07:00
committed by Alex Rickabaugh
parent 6790848f68
commit ca11ef2376
53 changed files with 3270 additions and 1643 deletions
+18 -3
View File
@@ -223,6 +223,7 @@
"packages/core/src/render3/assert.ts",
"packages/core/src/render3/interfaces/container.ts",
"packages/core/src/render3/interfaces/node.ts",
"packages/core/src/render3/interfaces/i18n.ts",
"packages/core/src/render3/interfaces/view.ts",
"packages/core/src/di/injector.ts",
"packages/core/src/di/r3_injector.ts",
@@ -239,6 +240,7 @@
"packages/core/src/render3/assert.ts",
"packages/core/src/render3/interfaces/container.ts",
"packages/core/src/render3/interfaces/node.ts",
"packages/core/src/render3/interfaces/i18n.ts",
"packages/core/src/render3/interfaces/view.ts",
"packages/core/src/metadata.ts",
"packages/core/src/di.ts",
@@ -262,6 +264,7 @@
"packages/core/src/render3/assert.ts",
"packages/core/src/render3/interfaces/container.ts",
"packages/core/src/render3/interfaces/node.ts",
"packages/core/src/render3/interfaces/i18n.ts",
"packages/core/src/render3/interfaces/view.ts",
"packages/core/src/render3/interfaces/definition.ts",
"packages/core/src/core.ts",
@@ -968,11 +971,13 @@
[
"packages/core/src/render3/interfaces/container.ts",
"packages/core/src/render3/interfaces/node.ts",
"packages/core/src/render3/interfaces/i18n.ts",
"packages/core/src/render3/interfaces/view.ts"
],
[
"packages/core/src/render3/interfaces/definition.ts",
"packages/core/src/render3/interfaces/node.ts",
"packages/core/src/render3/interfaces/i18n.ts",
"packages/core/src/render3/interfaces/view.ts"
],
[
@@ -980,13 +985,23 @@
"packages/core/src/render3/interfaces/view.ts"
],
[
"packages/core/src/render3/interfaces/node.ts",
"packages/core/src/render3/interfaces/i18n.ts",
"packages/core/src/render3/interfaces/node.ts"
],
[
"packages/core/src/render3/interfaces/i18n.ts",
"packages/core/src/render3/interfaces/view.ts"
],
[
"packages/core/src/render3/interfaces/node.ts",
"packages/core/src/render3/interfaces/i18n.ts",
"packages/core/src/render3/interfaces/view.ts",
"packages/core/src/render3/interfaces/query.ts"
"packages/core/src/render3/interfaces/node.ts"
],
[
"packages/core/src/render3/interfaces/i18n.ts",
"packages/core/src/render3/interfaces/view.ts",
"packages/core/src/render3/interfaces/query.ts",
"packages/core/src/render3/interfaces/node.ts"
],
[
"packages/core/src/render3/interfaces/query.ts",
+1 -1
View File
@@ -799,7 +799,7 @@ export declare abstract class Renderer2 {
abstract createElement(name: string, namespace?: string | null): any;
abstract createText(value: string): any;
abstract destroy(): void;
abstract insertBefore(parent: any, newChild: any, refChild: any): void;
abstract insertBefore(parent: any, newChild: any, refChild: any, isMove?: boolean): void;
abstract listen(target: 'window' | 'document' | 'body' | any, eventName: string, callback: (event: any) => boolean | void): () => void;
abstract nextSibling(node: any): any;
abstract parentNode(node: any): any;
+1 -1
View File
@@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 3037,
"main-es2015": 447742,
"main-es2015": 448676,
"polyfills-es2015": 52415
}
}
@@ -3,7 +3,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 140199,
"main-es2015": 140899,
"polyfills-es2015": 36571
}
}
@@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 16650,
"main-es2015": 17092,
"polyfills-es2015": 36657
}
}
@@ -21,7 +21,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 146417,
"main-es2015": 147242,
"polyfills-es2015": 36571
}
}
@@ -30,7 +30,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 135003,
"main-es2015": 136096,
"polyfills-es2015": 37248
}
}
@@ -39,7 +39,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2289,
"main-es2015": 241850,
"main-es2015": 242460,
"polyfills-es2015": 36938,
"5-es2015": 751
}
@@ -49,7 +49,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2289,
"main-es2015": 217827,
"main-es2015": 218527,
"polyfills-es2015": 36723,
"5-es2015": 781
}
+6 -1
View File
@@ -158,8 +158,13 @@ export abstract class Renderer2 {
* @param parent The parent node.
* @param newChild The new child nodes.
* @param refChild The existing child node before which `newChild` is inserted.
* @param isMove Optional argument which signifies if the current `insertBefore` is a result of a
* move. Animation uses this information to trigger move animations. In the past the Animation
* would always assume that any `insertBefore` is a move. This is not strictly true because
* with runtime i18n it is possible to invoke `insertBefore` as a result of i18n and it should
* not trigger an animation move.
*/
abstract insertBefore(parent: any, newChild: any, refChild: any): void;
abstract insertBefore(parent: any, newChild: any, refChild: any, isMove?: boolean): void;
/**
* Implement this callback to remove a child node from the host element's DOM.
* @param parent The parent node.
+22 -9
View File
@@ -10,6 +10,7 @@ import {assertDefined, assertEqual, assertNumber, throwError} from '../util/asse
import {getComponentDef, getNgModuleDef} from './definition';
import {LContainer} from './interfaces/container';
import {DirectiveDef} from './interfaces/definition';
import {TIcu} from './interfaces/i18n';
import {NodeInjectorOffset} from './interfaces/injector';
import {TNode} from './interfaces/node';
import {isLContainer, isLView} from './interfaces/type_checks';
@@ -25,13 +26,28 @@ export function assertTNodeForLView(tNode: TNode, lView: LView) {
}
export function assertTNodeForTView(tNode: TNode, tView: TView) {
assertDefined(tNode, 'TNode must be defined');
assertTNode(tNode);
tNode.hasOwnProperty('tView_') &&
assertEqual(
(tNode as any as {tView_: TView}).tView_, tView,
'This TNode does not belong to this TView.');
}
export function assertTNode(tNode: TNode) {
assertDefined(tNode, 'TNode must be defined');
if (!(tNode && typeof tNode === 'object' && tNode.hasOwnProperty('directiveStylingLast'))) {
throwError('Not of type TNode, got: ' + tNode);
}
}
export function assertTIcu(tIcu: TIcu) {
assertDefined(tIcu, 'Expected TIcu to be defined');
if (!(typeof tIcu.currentCaseLViewIndex === 'number')) {
throwError('Object is not of TIcu type.');
}
}
export function assertComponentType(
actual: any,
msg: string = 'Type passed in is not ComponentType, it does not have \'ɵcmp\' property.') {
@@ -106,18 +122,15 @@ export function assertIndexInDeclRange(lView: LView, index: number) {
export function assertIndexInVarsRange(lView: LView, index: number) {
const tView = lView[1];
assertBetween(
tView.bindingStartIndex, (tView as any as {i18nStartIndex: number}).i18nStartIndex, index);
}
export function assertIndexInI18nRange(lView: LView, index: number) {
const tView = lView[1];
assertBetween(
(tView as any as {i18nStartIndex: number}).i18nStartIndex, tView.expandoStartIndex, index);
tView.bindingStartIndex,
(tView as any as {originalExpandoStartIndex: number}).originalExpandoStartIndex, index);
}
export function assertIndexInExpandoRange(lView: LView, index: number) {
const tView = lView[1];
assertBetween(tView.expandoStartIndex, lView.length, index);
assertBetween(
(tView as any as {originalExpandoStartIndex: number}).originalExpandoStartIndex, lView.length,
index);
}
export function assertBetween(lower: number, upper: number, index: number) {
+5 -1
View File
@@ -163,6 +163,7 @@ export function renderComponent<T>(
* @param rNode Render host element.
* @param def ComponentDef
* @param rootView The parent view where the host node is stored
* @param rendererFactory Factory to be used for creating child renderers.
* @param hostRenderer The current renderer
* @param sanitizer The sanitizer, if provided
*
@@ -174,7 +175,10 @@ export function createRootComponentView(
const tView = rootView[TVIEW];
ngDevMode && assertIndexInRange(rootView, 0 + HEADER_OFFSET);
rootView[0 + HEADER_OFFSET] = rNode;
const tNode: TElementNode = getOrCreateTNode(tView, 0, TNodeType.Element, null, null);
// '#host' is added here as we don't know the real host DOM name (we don't want to read it) and at
// the same time we want to communicate the the debug `TNode` that this is a special `TNode`
// representing a host element.
const tNode = getOrCreateTNode(tView, 0, TNodeType.Element, '#host', null);
const mergedAttrs = tNode.mergedAttrs = def.hostAttrs;
if (mergedAttrs !== null) {
computeStaticStyling(tNode, mergedAttrs, true);
+4 -4
View File
@@ -23,13 +23,13 @@ import {assertComponentType} from './assert';
import {createRootComponent, createRootComponentView, createRootContext, LifecycleHooksFeature} from './component';
import {getComponentDef} from './definition';
import {NodeInjector} from './di';
import {createLView, createTView, elementCreate, locateHostElement, renderView} from './instructions/shared';
import {createLView, createTView, locateHostElement, renderView} from './instructions/shared';
import {ComponentDef} from './interfaces/definition';
import {TContainerNode, TElementContainerNode, TElementNode, TNode} from './interfaces/node';
import {domRendererFactory3, RendererFactory3, RNode} from './interfaces/renderer';
import {LView, LViewFlags, TViewType} from './interfaces/view';
import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces';
import {writeDirectClass} from './node_manipulation';
import {createElementNode, writeDirectClass} from './node_manipulation';
import {extractAttrsAndClassesFromSelector, stringifyCSSSelectorList} from './node_selector_matcher';
import {enterView, leaveView} from './state';
import {setUpAttributes} from './util/attrs_utils';
@@ -147,8 +147,8 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
const elementName = this.componentDef.selectors[0][0] as string || 'div';
const hostRNode = rootSelectorOrNode ?
locateHostElement(hostRenderer, rootSelectorOrNode, this.componentDef.encapsulation) :
elementCreate(
elementName, rendererFactory.createRenderer(null, this.componentDef),
createElementNode(
rendererFactory.createRenderer(null, this.componentDef), elementName,
getNamespace(elementName));
const rootFlags = this.componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
@@ -204,11 +204,7 @@ function findViaNativeElement(lView: LView, target: RElement): number {
* Locates the next tNode (child, sibling or parent).
*/
function traverseNextElement(tNode: TNode): TNode|null {
if (tNode.child && tNode.child.parent === tNode) {
// FIXME(misko): checking if `tNode.child.parent === tNode` should not be necessary
// We have added it here because i18n creates TNode's which are not valid, so this is a work
// around. The i18n code will be refactored in #39003 and once it lands this extra check can be
// deleted.
if (tNode.child) {
return tNode.child;
} else if (tNode.next) {
return tNode.next;
-4
View File
@@ -115,10 +115,6 @@ The i18n markers are:
- `index`: the index of the `template` instruction, as defined in the template instructions (e.g. `template(index, ...)`).
- `block`: the index of the parent sub-template block, in which this child sub-template block was declared.
- `�!{index}:{block}�/�/!{index}:{block}�`: *Projection block*: Marks the beginning and end of <ng-content> that was embedded in the original translation block.
- `index`: the index of the projection, as defined in the template instructions (e.g. `projection(index, ...)`).
- `block` (*optional*): the index of the parent sub-template block, in which this child sub-template block was declared.
No other i18n marker format is supported.
The i18n markers in the example above can be interpreted as follows:
File diff suppressed because it is too large Load Diff
+36 -9
View File
@@ -7,8 +7,38 @@
*/
import {assertNumber, assertString} from '../../util/assert';
import {COMMENT_MARKER, ELEMENT_MARKER, getInstructionFromI18nMutateOpCode, getParentFromI18nMutateOpCode, getRefFromI18nMutateOpCode, I18nCreateOpCode, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes} from '../interfaces/i18n';
import {COMMENT_MARKER, ELEMENT_MARKER, getInstructionFromI18nMutateOpCode, getParentFromI18nMutateOpCode, getRefFromI18nMutateOpCode, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes} from '../interfaces/i18n';
/**
* Converts `I18nCreateOpCodes` array into a human readable format.
*
* This function is attached to the `I18nCreateOpCodes.debug` property if `ngDevMode` is enabled.
* This function provides a human readable view of the opcodes. This is useful when debugging the
* application as well as writing more readable tests.
*
* @param this `I18nCreateOpCodes` if attached as a method.
* @param opcodes `I18nCreateOpCodes` if invoked as a function.
*/
export function i18nCreateOpCodesToString(
this: I18nUpdateOpCodes|void, opcodes?: I18nUpdateOpCodes): string[] {
const createOpCodes: I18nUpdateOpCodes = opcodes || (Array.isArray(this) ? this : []);
let lines: string[] = [];
for (let i = 0; i < createOpCodes.length; i++) {
const opCode = createOpCodes[i++] as any;
const text = createOpCodes[i] as string;
const isComment = (opCode & I18nCreateOpCode.COMMENT) === I18nCreateOpCode.COMMENT;
const appendNow =
(opCode & I18nCreateOpCode.APPEND_EAGERLY) === I18nCreateOpCode.APPEND_EAGERLY;
const index = opCode >>> I18nCreateOpCode.SHIFT;
lines.push(`lView[${index}] = document.${isComment ? 'createComment' : 'createText'}(${
JSON.stringify(text)});`);
if (appendNow) {
lines.push(`parent.appendChild(lView[${index}]);`);
}
}
return lines;
}
/**
* Converts `I18nUpdateOpCodes` array into a human readable format.
@@ -37,9 +67,9 @@ export function i18nUpdateOpCodesToString(
const value = sanitizationFn ? `(${sanitizationFn})($$$)` : '$$$';
return `(lView[${ref}] as Element).setAttribute('${attrName}', ${value})`;
case I18nUpdateOpCode.IcuSwitch:
return `icuSwitchCase(lView[${ref}] as Comment, ${parser.consumeNumber()}, $$$)`;
return `icuSwitchCase(${ref}, $$$)`;
case I18nUpdateOpCode.IcuUpdate:
return `icuUpdateCase(lView[${ref}] as Comment, ${parser.consumeNumber()})`;
return `icuUpdateCase(${ref})`;
}
throw new Error('unexpected OpCode');
}
@@ -57,7 +87,9 @@ export function i18nUpdateOpCodesToString(
statement += value;
} else if (value < 0) {
// Negative numbers are ref indexes
statement += '${lView[' + (0 - value) + ']}';
// Here `i` refers to current binding index. It is to signify that the value is relative,
// rather than absolute.
statement += '${lView[i' + value + ']}';
} else {
// Positive numbers are operations.
const opCodeText = consumeOpCode(value);
@@ -89,9 +121,6 @@ export function i18nMutateOpCodesToString(
const parent = getParentFromI18nMutateOpCode(opCode);
const ref = getRefFromI18nMutateOpCode(opCode);
switch (getInstructionFromI18nMutateOpCode(opCode)) {
case I18nMutateOpCode.Select:
lastRef = ref;
return '';
case I18nMutateOpCode.AppendChild:
return `(lView[${parent}] as Element).appendChild(lView[${lastRef}])`;
case I18nMutateOpCode.Remove:
@@ -99,8 +128,6 @@ export function i18nMutateOpCodesToString(
case I18nMutateOpCode.Attr:
return `(lView[${ref}] as Element).setAttribute("${parser.consumeString()}", "${
parser.consumeString()}")`;
case I18nMutateOpCode.ElementEnd:
return `setCurrentTNode(tView.data[${ref}] as TNode)`;
case I18nMutateOpCode.RemoveNestedIcu:
return `removeNestedICU(${ref})`;
}
@@ -0,0 +1,86 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {assertEqual} from '../../util/assert';
import {TNode, TNodeType} from '../interfaces/node';
/**
* Add `tNode` to `previousTNodes` list and update relevant `TNode`s in `previousTNodes` list
* `tNode.insertBeforeIndex`.
*
* Things to keep in mind:
* 1. All i18n text nodes are encoded as `TNodeType.Element` and are created eagerly by the
* `ɵɵi18nStart` instruction.
* 2. All `TNodeType.Placeholder` `TNodes` are elements which will be created later by
* `ɵɵelementStart` instruction.
* 3. `ɵɵelementStart` instruction will create `TNode`s in the ascending `TNode.index` order. (So a
* smaller index `TNode` is guaranteed to be created before a larger one)
*
* We use the above three invariants to determine `TNode.insertBeforeIndex`.
*
* In an ideal world `TNode.insertBeforeIndex` would always be `TNode.next.index`. However,
* this will not work because `TNode.next.index` may be larger than `TNode.index` which means that
* the next node is not yet created and therefore we can't insert in front of it.
*
* Rule1: `TNode.insertBeforeIndex = null` if `TNode.next === null` (Initial condition, as we don't
* know if there will be further `TNode`s inserted after.)
* Rule2: If `previousTNode` is created after the `tNode` being inserted, then
* `previousTNode.insertBeforeNode = tNode.index` (So when a new `tNode` is added we check
* previous to see if we can update its `insertBeforeTNode`)
*
* See `TNode.insertBeforeIndex` for more context.
*
* @param previousTNodes A list of previous TNodes so that we can easily traverse `TNode`s in
* reverse order. (If `TNode` would have `previous` this would not be necessary.)
* @param newTNode A TNode to add to the `previousTNodes` list.
*/
export function addTNodeAndUpdateInsertBeforeIndex(previousTNodes: TNode[], newTNode: TNode) {
// Start with Rule1
ngDevMode &&
assertEqual(newTNode.insertBeforeIndex, null, 'We expect that insertBeforeIndex is not set');
previousTNodes.push(newTNode);
if (previousTNodes.length > 1) {
for (let i = previousTNodes.length - 2; i >= 0; i--) {
const existingTNode = previousTNodes[i];
// Text nodes are created eagerly and so they don't need their `indexBeforeIndex` updated.
// It is safe to ignore them.
if (!isI18nText(existingTNode)) {
if (isNewTNodeCreatedBefore(existingTNode, newTNode) &&
getInsertBeforeIndex(existingTNode) === null) {
// If it was created before us in time, (and it does not yet have `insertBeforeIndex`)
// then add the `insertBeforeIndex`.
setInsertBeforeIndex(existingTNode, newTNode.index);
}
}
}
}
}
function isI18nText(tNode: TNode): boolean {
return tNode.type !== TNodeType.Placeholder;
}
function isNewTNodeCreatedBefore(existingTNode: TNode, newTNode: TNode): boolean {
return isI18nText(newTNode) || existingTNode.index > newTNode.index;
}
function getInsertBeforeIndex(tNode: TNode): number|null {
const index = tNode.insertBeforeIndex;
return Array.isArray(index) ? index[0] : index;
}
function setInsertBeforeIndex(tNode: TNode, value: number): void {
const index = tNode.insertBeforeIndex;
if (Array.isArray(index)) {
// Array is stored if we have to insert child nodes. See `TNode.insertBeforeIndex`
index[0] = value;
} else {
tNode.insertBeforeIndex = value;
}
}
File diff suppressed because it is too large Load Diff
+107
View File
@@ -0,0 +1,107 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {assertEqual, throwError} from '../../util/assert';
import {assertTIcu, assertTNode} from '../assert';
import {createTNodeAtIndex} from '../instructions/shared';
import {TIcu} from '../interfaces/i18n';
import {TIcuContainerNode, TNode, TNodeType} from '../interfaces/node';
import {TView} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {addTNodeAndUpdateInsertBeforeIndex} from './i18n_insert_before_index';
/**
* Retrieve `TIcu` at a given `index`.
*
* The `TIcu` can be stored either directly (if it is nested ICU) OR
* it is stored inside tho `TIcuContainer` if it is top level ICU.
*
* The reason for this is that the top level ICU need a `TNode` so that they are part of the render
* tree, but nested ICU's have no TNode, because we don't know ahead of time if the nested ICU is
* expressed (parent ICU may have selected a case which does not contain it.)
*
* @param tView Current `TView`.
* @param index Index where the value should be read from.
*/
export function getTIcu(tView: TView, index: number): TIcu|null {
const value = tView.data[index] as null | TIcu | TIcuContainerNode | string;
if (value === null || typeof value === 'string') return null;
if (ngDevMode &&
!(value.hasOwnProperty('tViews') || value.hasOwnProperty('currentCaseLViewIndex'))) {
throwError('We expect to get \'null\'|\'TIcu\'|\'TIcuContainer\', but got: ' + value);
}
// Here the `value.hasOwnProperty('currentCaseLViewIndex')` is a polymorphic read as it can be
// either TIcu or TIcuContainerNode. This is not ideal, but we still think it is OK because it
// will be just two cases which fits into the browser inline cache (inline cache can take up to
// 4)
const tIcu = value.hasOwnProperty('currentCaseLViewIndex') ?
value :
(value as TIcuContainerNode).tagName as any;
ngDevMode && assertTIcu(tIcu);
return tIcu;
}
/**
* Store `TIcu` at a give `index`.
*
* The `TIcu` can be stored either directly (if it is nested ICU) OR
* it is stored inside tho `TIcuContainer` if it is top level ICU.
*
* The reason for this is that the top level ICU need a `TNode` so that they are part of the render
* tree, but nested ICU's have no TNode, because we don't know ahead of time if the nested ICU is
* expressed (parent ICU may have selected a case which does not contain it.)
*
* @param tView Current `TView`.
* @param index Index where the value should be stored at in `Tview.data`
* @param tIcu The TIcu to store.
*/
export function setTIcu(tView: TView, index: number, tIcu: TIcu): void {
const tNode = tView.data[index] as null | TIcuContainerNode;
ngDevMode &&
assertEqual(
tNode === null || tNode.hasOwnProperty('tViews'), true,
'We expect to get \'null\'|\'TIcuContainer\'');
if (tNode === null) {
tView.data[index] = tIcu;
} else {
ngDevMode && assertNodeType(tNode, TNodeType.IcuContainer);
// FIXME(misko): This is a hack which allows us to associate `TI18n` with `TNode`.
// This should be refactored so that one can attach arbitrary data with `TNode`
tNode.tagName = tIcu as any;
}
}
/**
* Set `TNode.insertBeforeIndex` taking the `Array` into account.
*
* See `TNode.insertBeforeIndex`
*/
export function setTNodeInsertBeforeIndex(tNode: TNode, index: number) {
ngDevMode && assertTNode(tNode);
let insertBeforeIndex = tNode.insertBeforeIndex;
if (insertBeforeIndex === null) {
insertBeforeIndex = tNode.insertBeforeIndex =
[null!/* may be updated to number later */, index];
} else {
assertEqual(Array.isArray(insertBeforeIndex), true, 'Expecting array here');
(insertBeforeIndex as number[]).push(index);
}
}
/**
* Create `TNode.type=TNodeType.Placeholder` node.
*
* See `TNodeType.Placeholder` for more information.
*/
export function createTNodePlaceholder(
tView: TView, previousTNodes: TNode[], index: number): TNode {
const tNode = createTNodeAtIndex(tView, index, TNodeType.Placeholder, null, null);
addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tNode);
return tNode;
}
@@ -10,19 +10,18 @@ import {assertDefined, assertEqual, assertIndexInRange} from '../../util/assert'
import {assertFirstCreatePass, assertHasParent} from '../assert';
import {attachPatchData} from '../context_discovery';
import {registerPostOrderHooks} from '../hooks';
import {hasClassInput, hasStyleInput, TAttributes, TElementNode, TNode, TNodeType} from '../interfaces/node';
import {hasClassInput, hasStyleInput, TAttributes, TElementNode, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
import {RElement} from '../interfaces/renderer';
import {isContentQueryHost, isDirectiveHost} from '../interfaces/type_checks';
import {HEADER_OFFSET, LView, RENDERER, T_HOST, TVIEW, TView} from '../interfaces/view';
import {HEADER_OFFSET, LView, RENDERER, TView} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {appendChild, 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 {computeStaticStyling} from '../styling/static_styling';
import {setUpAttributes} from '../util/attrs_utils';
import {getConstant} from '../util/view_utils';
import {setDirectiveInputsWhichShadowsStyling} from './property';
import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, matchingSchemas, resolveDirectives, saveResolvedLocalsInData} from './shared';
import {createDirectivesInstances, executeContentQueries, getOrCreateTNode, matchingSchemas, resolveDirectives, saveResolvedLocalsInData} from './shared';
function elementStartFirstCreatePass(
@@ -78,12 +77,10 @@ export function ɵɵelementStart(
assertEqual(
getBindingIndex(), tView.bindingStartIndex,
'elements should be created before any bindings');
ngDevMode && ngDevMode.rendererCreateElement++;
ngDevMode && assertIndexInRange(lView, adjustedIndex);
const renderer = lView[RENDERER];
const native = lView[adjustedIndex] = elementCreate(name, renderer, getNamespace());
const native = lView[adjustedIndex] = createElementNode(renderer, name, getNamespace());
const tNode = tView.firstCreatePass ?
elementStartFirstCreatePass(index, tView, lView, native, name, attrsIndex, localRefsIndex) :
tView.data[adjustedIndex] as TElementNode;
@@ -102,7 +99,11 @@ export function ɵɵelementStart(
writeDirectStyle(renderer, native, styles);
}
appendChild(tView, lView, native, tNode);
if ((tNode.flags & TNodeFlags.isDetached) !== TNodeFlags.isDetached) {
// In the i18n case, the translation may have removed this element, so only add it if it is not
// detached. See `TNodeType.Placeholder` and `LFrame.inI18n` for more context.
appendChild(tView, lView, native, tNode);
}
// 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
+26 -22
View File
@@ -10,15 +10,16 @@ import '../../util/ng_i18n_closure_mode';
import {assertDefined} from '../../util/assert';
import {bindingUpdated} from '../bindings';
import {applyI18n, i18nEndFirstPass, pushI18nIndex, setMaskBit} from '../i18n/i18n_apply';
import {i18nAttributesFirstPass, i18nStartFirstPass} from '../i18n/i18n_parse';
import {applyCreateOpCodes, applyI18n, setMaskBit} from '../i18n/i18n_apply';
import {i18nAttributesFirstPass, i18nStartFirstCreatePass} from '../i18n/i18n_parse';
import {i18nPostprocess} from '../i18n/i18n_postprocess';
import {HEADER_OFFSET} from '../interfaces/view';
import {getLView, getTView, nextBindingIndex} from '../state';
import {TI18n} from '../interfaces/i18n';
import {TElementNode, TNodeType} from '../interfaces/node';
import {HEADER_OFFSET, T_HOST} from '../interfaces/view';
import {getClosestRElement} from '../node_manipulation';
import {getCurrentParentTNode, getLView, getTView, nextBindingIndex, setInI18nBlock} from '../state';
import {getConstant} from '../util/view_utils';
import {setDelayProjection} from './projection';
/**
* Marks a block of text as translatable.
*
@@ -34,10 +35,6 @@ import {setDelayProjection} from './projection';
* and end of DOM element that were embedded in the original translation block. The placeholder
* `index` points to the element index in the template instructions set. An optional `block` that
* matches the sub-template in which it was declared.
* - `�!{index}(:{block})�`/`�/!{index}(:{block})�`: *Projection Placeholder*: Marks the
* beginning and end of <ng-content> that was embedded in the original translation block.
* The placeholder `index` points to the element index in the template instructions set.
* An optional `block` that matches the sub-template in which it was declared.
* - `�*{index}:{block}�`/`�/*{index}:{block}�`: *Sub-template Placeholder*: Sub-templates must be
* split up and translated separately in each angular template function. The `index` points to the
* `template` instruction index. A `block` that matches the sub-template in which it was declared.
@@ -48,16 +45,28 @@ import {setDelayProjection} from './projection';
*
* @codeGenApi
*/
export function ɵɵi18nStart(index: number, messageIndex: number, subTemplateIndex?: number): void {
export function ɵɵi18nStart(
index: number, messageIndex: number, subTemplateIndex: number = -1): void {
const tView = getTView();
const lView = getLView();
ngDevMode && assertDefined(tView, `tView should be defined`);
const message = getConstant<string>(tView.consts, messageIndex)!;
pushI18nIndex(index);
// We need to delay projections until `i18nEnd`
setDelayProjection(true);
if (tView.firstCreatePass && tView.data[index + HEADER_OFFSET] === null) {
i18nStartFirstPass(getLView(), tView, index, message, subTemplateIndex);
const parentTNode = getCurrentParentTNode() as TElementNode | null;
if (tView.firstCreatePass) {
i18nStartFirstCreatePass(
tView, parentTNode === null ? 0 : parentTNode.index, lView, index, message,
subTemplateIndex);
}
const tI18n = tView.data[HEADER_OFFSET + index] as TI18n;
const sameViewParentTNode = parentTNode === lView[T_HOST] ? null : parentTNode;
const parentRNode = getClosestRElement(tView, sameViewParentTNode, lView);
// 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--->`.
const insertInFrontOf = parentTNode && parentTNode.type === TNodeType.ElementContainer ?
lView[parentTNode.index] :
null;
applyCreateOpCodes(lView, tI18n.create, parentRNode, insertInFrontOf);
setInI18nBlock(true);
}
@@ -69,12 +78,7 @@ export function ɵɵi18nStart(index: number, messageIndex: number, subTemplateIn
* @codeGenApi
*/
export function ɵɵi18nEnd(): void {
const lView = getLView();
const tView = getTView();
ngDevMode && assertDefined(tView, `tView should be defined`);
i18nEndFirstPass(tView, lView);
// Stop delaying projections
setDelayProjection(false);
setInI18nBlock(false);
}
/**
@@ -0,0 +1,93 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {assertDomNode, assertEqual, assertNumber, assertNumberInRange} from '../../util/assert';
import {assertTIcu, assertTNodeForLView} from '../assert';
import {EMPTY_ARRAY} from '../empty';
import {getCurrentICUCaseIndex, I18nMutateOpCode, I18nMutateOpCodes, TIcu} from '../interfaces/i18n';
import {TIcuContainerNode} from '../interfaces/node';
import {RNode} from '../interfaces/renderer';
import {LView, TVIEW} from '../interfaces/view';
export function loadIcuContainerVisitor() {
const _stack: any[] = [];
let _index: number = -1;
let _lView: LView;
let _removes: I18nMutateOpCodes;
/**
* Retrieves a set of root nodes from `TIcu.remove`. Used by `TNodeType.ICUContainer`
* to determine which root belong to the ICU.
*
* Example of usage.
* ```
* const nextRNode = icuContainerIteratorStart(tIcuContainerNode, lView);
* let rNode: RNode|null;
* while(rNode = nextRNode()) {
* console.log(rNode);
* }
* ```
*
* @param tIcuContainerNode Current `TIcuContainerNode`
* @param lView `LView` where the `RNode`s should be looked up.
*/
function icuContainerIteratorStart(tIcuContainerNode: TIcuContainerNode, lView: LView): () =>
RNode | null {
_lView = lView;
while (_stack.length) _stack.pop();
// FIXME(misko): This is a hack which allows us to associate `TI18n` with `TNode`.
// This should be refactored so that one can attach arbitrary data with `TNode`
ngDevMode && assertTNodeForLView(tIcuContainerNode, lView);
const tIcu: TIcu = tIcuContainerNode.tagName as any;
enterIcu(tIcu, lView);
return icuContainerIteratorNext;
}
function enterIcu(tIcu: TIcu, lView: LView) {
_index = 0;
const currentCase = getCurrentICUCaseIndex(tIcu, lView);
if (currentCase !== null) {
ngDevMode && assertNumberInRange(currentCase, 0, tIcu.cases.length - 1);
_removes = tIcu.remove[currentCase];
} else {
_removes = EMPTY_ARRAY;
}
}
function icuContainerIteratorNext(): RNode|null {
if (_index < _removes.length) {
const removeOpCode = _removes[_index++] as number;
ngDevMode && assertNumber(removeOpCode, 'Expecting OpCode number');
const opCode = removeOpCode & I18nMutateOpCode.MASK_INSTRUCTION;
if (opCode === I18nMutateOpCode.Remove) {
const rNode = _lView[removeOpCode >>> I18nMutateOpCode.SHIFT_REF];
ngDevMode && assertDomNode(rNode);
return rNode;
} else {
ngDevMode &&
assertEqual(opCode, I18nMutateOpCode.RemoveNestedIcu, 'Expecting RemoveNestedIcu');
_stack.push(_index, _removes);
const tIcu = _lView[TVIEW].data[removeOpCode >>> I18nMutateOpCode.SHIFT_REF] as TIcu;
ngDevMode && assertTIcu(tIcu);
enterIcu(tIcu, _lView);
return icuContainerIteratorNext();
}
} else {
if (_stack.length === 0) {
return null;
} else {
_removes = _stack.pop();
_index = _stack.pop();
return icuContainerIteratorNext();
}
}
}
return icuContainerIteratorStart;
}
@@ -17,7 +17,7 @@ import {getInjectorIndex} from '../di';
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
import {NO_PARENT_INJECTOR, NodeInjectorOffset} from '../interfaces/injector';
import {AttributeMarker, 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, TNodeTypeAsString} from '../interfaces/node';
import {SelectorFlags} from '../interfaces/projection';
import {LQueries, TQueries} from '../interfaces/query';
import {RComment, RElement, Renderer3, RendererFactory3, RNode} from '../interfaces/renderer';
@@ -160,7 +160,13 @@ export const TViewConstructor = class TView implements ITView {
return TViewTypeAsString[this.type] || `TViewType.?${this.type}?`;
}
get i18nStartIndex(): number {
/**
* Returns initial value of `expandoStartIndex`.
*/
// FIXME(misko): `originalExpandoStartIndex` should not be needed because it should be the same as
// `expandoStartIndex`. However `expandoStartIndex` is misnamed as it changes as more items get
// allocated in expando.
get originalExpandoStartIndex(): number {
return HEADER_OFFSET + this._decls + this._vars;
}
};
@@ -170,6 +176,7 @@ class TNode implements ITNode {
public tView_: TView, //
public type: TNodeType, //
public index: number, //
public insertBeforeIndex: InsertBeforeIndex, //
public injectorIndex: number, //
public directiveStart: number, //
public directiveEnd: number, //
@@ -249,8 +256,13 @@ class TNode implements ITNode {
}
get template_(): string {
if (this.tagName === null && this.type === TNodeType.Element) return '#text';
const buf: string[] = [];
buf.push('<', this.tagName || this.type_);
const tagName = typeof this.tagName === 'string' && this.tagName || this.type_;
buf.push('<', tagName);
if (this.flags) {
buf.push(' ', this.flags_);
}
if (this.attrs) {
for (let i = 0; i < this.attrs.length;) {
const attrName = this.attrs[i++];
@@ -263,7 +275,7 @@ class TNode implements ITNode {
}
buf.push('>');
processTNodeChildren(this.child, buf);
buf.push('</', this.tagName || this.type_, '>');
buf.push('</', tagName, '>');
return buf.join('');
}
@@ -444,7 +456,7 @@ export class LViewDebug implements ILViewDebug {
return toHtml(this._raw_lView[HOST], true);
}
get html(): string {
return (this.nodes || []).map(node => toHtml(node.native, true)).join('');
return (this.nodes || []).map(mapToHTML).join('');
}
get context(): {}|null {
return this._raw_lView[CONTEXT];
@@ -458,7 +470,9 @@ export class LViewDebug implements ILViewDebug {
const tNode = lView[TVIEW].firstChild;
return toDebugNodes(tNode, lView);
}
get template(): string {
return (this.tView as any as {template_: string}).template_;
}
get tView(): ITView {
return this._raw_lView[TVIEW];
}
@@ -504,20 +518,15 @@ export class LViewDebug implements ILViewDebug {
const tView = this.tView;
return toLViewRange(
tView, this._raw_lView, tView.bindingStartIndex,
(tView as any as {i18nStartIndex: number}).i18nStartIndex);
}
get i18n(): LViewDebugRange {
const tView = this.tView;
return toLViewRange(
tView, this._raw_lView, (tView as any as {i18nStartIndex: number}).i18nStartIndex,
tView.expandoStartIndex);
(tView as any as {originalExpandoStartIndex: number}).originalExpandoStartIndex);
}
get expando(): LViewDebugRange {
const tView = this.tView as any as {_decls: number, _vars: number};
return toLViewRange(
this.tView, this._raw_lView, this.tView.expandoStartIndex, this._raw_lView.length);
this.tView, this._raw_lView,
(tView as any as {originalExpandoStartIndex: number}).originalExpandoStartIndex,
this._raw_lView.length);
}
/**
@@ -534,6 +543,16 @@ export class LViewDebug implements ILViewDebug {
}
}
function mapToHTML(node: DebugNode): string {
if (node.type === 'ElementContainer') {
return (node.children || []).map(mapToHTML).join('');
} else if (node.type === 'IcuContainer') {
throw new Error('Not implemented');
} else {
return toHtml(node.native, true) || '';
}
}
function toLViewRange(tView: TView, lView: LView, start: number, end: number): LViewDebugRange {
let content: LViewDebugRangeContent[] = [];
for (let index = start; index < end; index++) {
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {newArray} from '../../util/array_utils';
import {TAttributes, TElementNode, TNode, TNodeType} from '../interfaces/node';
import {TAttributes, TElementNode, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
import {ProjectionSlots} from '../interfaces/projection';
import {DECLARATION_COMPONENT_VIEW, T_HOST} from '../interfaces/view';
import {applyProjection} from '../node_manipulation';
@@ -103,11 +103,6 @@ export function ɵɵprojectionDef(projectionSlots?: ProjectionSlots): void {
}
}
let delayProjection = false;
export function setDelayProjection(value: boolean) {
delayProjection = value;
}
/**
* Inserts previously re-distributed projected nodes. This instruction must be preceded by a call
@@ -133,8 +128,7 @@ export function ɵɵprojection(
// `<ng-content>` has no content
setCurrentTNodeAsNotParent();
// We might need to delay the projection of nodes if they are in the middle of an i18n block
if (!delayProjection) {
if ((tProjectionNode.flags & TNodeFlags.isDetached) !== TNodeFlags.isDetached) {
// re-distribution of projectable nodes is stored on a component's view level
applyProjection(tView, lView, tProjectionNode);
}
+110 -90
View File
@@ -5,43 +5,43 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injector} from '../../di';
import {ErrorHandler} from '../../error_handler';
import {DoCheck, OnChanges, OnInit} from '../../interface/lifecycle_hooks';
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../metadata/schema';
import {ViewEncapsulation} from '../../metadata/view';
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
import {Sanitizer} from '../../sanitization/sanitizer';
import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertIndexInRange, assertLessThan, assertNotEqual, assertNotSame, assertSame} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {initNgDevMode} from '../../util/ng_dev_mode';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
import {stringify} from '../../util/stringify';
import {assertFirstCreatePass, assertLContainer, assertLView, assertTNodeForLView} from '../assert';
import {attachPatchData} from '../context_discovery';
import {getFactoryDef} from '../definition';
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di';
import {throwMultipleComponentError} from '../errors';
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} from '../hooks';
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 {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 {isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization';
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, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view';
import {assertNodeNotOfTypes, assertNodeOfPossibleTypes} from '../node_assert';
import {isInlineTemplate, isNodeMatchingSelectorList} from '../node_selector_matcher';
import {enterView, getBindingsEnabled, getCurrentDirectiveIndex, getCurrentTNode, getSelectedIndex, isCurrentTNodeParent, isInCheckNoChangesMode, leaveView, setBindingIndex, setBindingRootForHostBindings, setCurrentDirectiveIndex, setCurrentQueryIndex, setCurrentTNode, setIsInCheckNoChangesMode, setSelectedIndex} from '../state';
import {NO_CHANGE} from '../tokens';
import {isAnimationProp, mergeHostAttrs} from '../util/attrs_utils';
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_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 {selectIndexInternal} from './advance';
import {attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData, LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor} from './lview_debug';
import { Injector } from '../../di';
import { ErrorHandler } from '../../error_handler';
import { DoCheck, OnChanges, OnInit } from '../../interface/lifecycle_hooks';
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata } from '../../metadata/schema';
import { ViewEncapsulation } from '../../metadata/view';
import { validateAgainstEventAttributes, validateAgainstEventProperties } from '../../sanitization/sanitization';
import { Sanitizer } from '../../sanitization/sanitizer';
import { assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertIndexInRange, assertLessThan, assertNotEqual, assertNotSame, assertSame, assertString } from '../../util/assert';
import { createNamedArrayType } from '../../util/named_array_type';
import { initNgDevMode } from '../../util/ng_dev_mode';
import { normalizeDebugBindingName, normalizeDebugBindingValue } from '../../util/ng_reflect';
import { stringify } from '../../util/stringify';
import { assertFirstCreatePass, assertFirstUpdatePass, assertLContainer, assertLView, assertTNodeForLView, assertTNodeForTView } from '../assert';
import { attachPatchData } from '../context_discovery';
import { getFactoryDef } from '../definition';
import { diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode } from '../di';
import { throwMultipleComponentError } from '../errors';
import { executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags } from '../hooks';
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 { 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 { isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText } from '../interfaces/renderer';
import { SanitizerFn } from '../interfaces/sanitization';
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 { assertNodeNotOfTypes, assertNodeOfPossibleTypes } from '../node_assert';
import { updateTextNode } from '../node_manipulation';
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 { NO_CHANGE } from '../tokens';
import { isAnimationProp, mergeHostAttrs } from '../util/attrs_utils';
import { INTERPOLATION_DELIMITER, renderStringify, stringifyForError } from '../util/misc_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 { selectIndexInternal } from './advance';
import { attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData, LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor } from './lview_debug';
@@ -155,21 +155,6 @@ function renderChildComponents(hostLView: LView, components: number[]): void {
}
}
/**
* Creates a native element from a tag name, using a renderer.
* @param name the tag name
* @param renderer A renderer to use
* @returns the element created
*/
export function elementCreate(name: string, renderer: Renderer3, namespace: string|null): RElement {
if (isProceduralRenderer(renderer)) {
return renderer.createElement(name, namespace);
} else {
return namespace === null ? renderer.createElement(name) :
renderer.createElementNS(namespace, name);
}
}
export function createLView<T>(
parentLView: LView|null, tView: TView, context: T|null, flags: LViewFlags, host: RElement|null,
tHostNode: TNode|null, rendererFactory: RendererFactory3|null, renderer: Renderer3|null,
@@ -230,17 +215,34 @@ export function getOrCreateTNode(
TElementNode&TContainerNode&TElementContainerNode&TProjectionNode&TIcuContainerNode {
// Keep this function short, so that the VM will inline it.
const adjustedIndex = index + HEADER_OFFSET;
const tNode = tView.data[adjustedIndex] as TNode ||
createTNodeAtIndex(tView, adjustedIndex, type, name, attrs);
let tNode = tView.data[adjustedIndex] as TNode;
if (tNode === null) {
tNode = createTNodeAtIndex(tView, adjustedIndex, type, name, attrs);
if (isInI18nBlock()) {
// If we are in i18n block then all elements should be pre declared through `Placeholder`
// See `TNodeType.Placeholder` and `LFrame.inI18n` for more context.
// If the `TNode` was not pre-declared than it means it was not mentioned which means it was
// removed, so we mark it as detached.
tNode.flags |= TNodeFlags.isDetached;
}
} else if (tNode.type == TNodeType.Placeholder) {
tNode.type = type;
tNode.tagName = name;
tNode.attrs = attrs;
const parent = getCurrentParentTNode();
tNode.injectorIndex = parent === null ? -1 : parent.injectorIndex;
ngDevMode && assertTNodeForTView(tNode, tView);
ngDevMode && assertEqual(index + HEADER_OFFSET, tNode.index, 'Expecting same index');
}
setCurrentTNode(tNode, true);
return tNode as TElementNode & TContainerNode & TElementContainerNode & TProjectionNode &
TIcuContainerNode;
}
function createTNodeAtIndex(
export function createTNodeAtIndex(
tView: TView, adjustedIndex: number, type: TNodeType, name: string|null,
attrs: TAttributes|null) {
const currentTNode = getCurrentTNode();
const currentTNode = getCurrentTNodePlaceholderOk();
const isParent = isCurrentTNodeParent();
const parent = isParent ? currentTNode : currentTNode && currentTNode.parent;
// Parents cannot cross component boundaries because components will be used in multiple places.
@@ -253,11 +255,18 @@ function createTNodeAtIndex(
tView.firstChild = tNode;
}
if (currentTNode !== null) {
if (isParent && currentTNode.child == null && tNode.parent !== null) {
// We are in the same view, which means we are adding content node to the parent view.
currentTNode.child = tNode;
} else if (!isParent) {
currentTNode.next = tNode;
if (isParent) {
// FIXME(misko): This logic looks unnecessarily complicated. Could we simplify?
if (currentTNode.child == null && tNode.parent !== null) {
// We are in the same view, which means we are adding content node to the parent view.
currentTNode.child = tNode;
}
} else {
if (currentTNode.next === null) {
// In the case of i18n the `currentTNode` may already be linked, in which case we don't want
// to break the links which i18n created.
currentTNode.next = tNode;
}
}
}
return tNode;
@@ -266,36 +275,40 @@ function createTNodeAtIndex(
/**
* When elements are created dynamically after a view blueprint is created (e.g. through
* i18nApply() or ComponentFactory.create), we need to adjust the blueprint for future
* i18nApply()), we need to adjust the blueprint for future
* template passes.
*
* @param tView `TView` associated with `LView`
* @param view The `LView` containing the blueprint to adjust
* @param lView The `LView` containing the blueprint to adjust
* @param numSlotsToAlloc The number of slots to alloc in the LView, should be >0
*/
export function allocExpando(tView: TView, lView: LView, numSlotsToAlloc: number) {
ngDevMode &&
assertGreaterThan(
numSlotsToAlloc, 0, 'The number of slots to alloc should be greater than 0');
if (numSlotsToAlloc > 0) {
if (tView.firstCreatePass) {
for (let i = 0; i < numSlotsToAlloc; i++) {
tView.blueprint.push(null);
tView.data.push(null);
lView.push(null);
}
// We should only increment the expando start index if there aren't already directives
// and injectors saved in the "expando" section
if (!tView.expandoInstructions) {
tView.expandoStartIndex += numSlotsToAlloc;
} else {
// Since we're adding the dynamic nodes into the expando section, we need to let the host
// bindings know that they should skip x slots
tView.expandoInstructions.push(numSlotsToAlloc);
}
}
export function allocExpando(tView: TView, lView: LView, numSlotsToAlloc: number): number {
if (ngDevMode) {
assertGreaterThan(numSlotsToAlloc, 0, 'The number of slots to alloc should be greater than 0');
assertEqual(tView.data.length, lView.length, 'Expecting LView to be same size as TView');
assertEqual(
tView.data.length, tView.blueprint.length, 'Expecting Blueprint to be same size as TView');
assertFirstUpdatePass(tView);
}
const allocIdx = lView.length;
for (let i = 0; i < numSlotsToAlloc; i++) {
tView.blueprint.push(null);
tView.data.push(null);
lView.push(null);
}
// We should only increment the expando start index if there aren't already directives
// and injectors saved in the "expando" section
if (!tView.expandoInstructions) {
tView.expandoStartIndex += numSlotsToAlloc;
} else {
// Since we're adding the dynamic nodes into the expando section, we need to let the host
// bindings know that they should skip x slots
// FIXME(misko): Refactor `expandoInstructions` so that it does not rely on relative binding
// offsets, but absolute values which Means we would not have to store it here.
tView.expandoInstructions.push(numSlotsToAlloc);
}
return allocIdx;
}
@@ -824,12 +837,14 @@ export function createTNode(
tagName: string|null, attrs: TAttributes|null): TNode {
ngDevMode && assertNotSame(attrs, undefined, '\'undefined\' is not valid value for \'attrs\'');
ngDevMode && ngDevMode.tNode++;
ngDevMode && tParent && assertTNodeForTView(tParent, tView);
let injectorIndex = tParent ? tParent.injectorIndex : -1;
const tNode = ngDevMode ?
new TNodeDebug(
tView, // tView_: TView
type, // type: TNodeType
adjustedIndex, // index: number
null, // insertBeforeIndex: null|-1|number|number[]
injectorIndex, // injectorIndex: number
-1, // directiveStart: number
-1, // directiveEnd: number
@@ -862,6 +877,7 @@ export function createTNode(
{
type: type,
index: adjustedIndex,
insertBeforeIndex: null,
injectorIndex: injectorIndex,
directiveStart: -1,
directiveEnd: -1,
@@ -1509,7 +1525,12 @@ export function elementAttributeInternal(
`Host bindings are not valid on ng-container or ng-template.`);
}
const element = getNativeByTNode(tNode, lView) as RElement;
const renderer = lView[RENDERER];
setElementAttribute(lView[RENDERER], element, namespace, tNode.tagName, name, value, sanitizer);
}
export function setElementAttribute(
renderer: Renderer3, element: RElement, namespace: string|null|undefined, tagName: string|null,
name: string, value: any, sanitizer: SanitizerFn|null|undefined) {
if (value == null) {
ngDevMode && ngDevMode.rendererRemoveAttribute++;
isProceduralRenderer(renderer) ? renderer.removeAttribute(element, name, namespace) :
@@ -1517,7 +1538,7 @@ export function elementAttributeInternal(
} else {
ngDevMode && ngDevMode.rendererSetAttribute++;
const strValue =
sanitizer == null ? renderStringify(value) : sanitizer(value, tNode.tagName || '', name);
sanitizer == null ? renderStringify(value) : sanitizer(value, tagName || '', name);
if (isProceduralRenderer(renderer)) {
@@ -2065,11 +2086,10 @@ export function setInputsForProperty(
* Updates a text binding at a given index in a given LView.
*/
export function textBindingInternal(lView: LView, index: number, value: string): void {
ngDevMode && assertString(value, 'Value should be a string');
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'value should not be NO_CHANGE');
ngDevMode && assertIndexInRange(lView, index + HEADER_OFFSET);
const element = getNativeByIndex(index, lView) as any as RText;
ngDevMode && assertDefined(element, 'native element should exist');
ngDevMode && ngDevMode.rendererSetText++;
const renderer = lView[RENDERER];
isProceduralRenderer(renderer) ? renderer.setValue(element, value) : element.textContent = value;
updateTextNode(lView[RENDERER], element, value);
}
@@ -38,7 +38,7 @@ export function ɵɵtext(index: number, value: string = ''): void {
getOrCreateTNode(tView, index, TNodeType.Element, null, null) :
tView.data[adjustedIndex] as TElementNode;
const textNative = lView[adjustedIndex] = createTextNode(value, lView[RENDERER]);
const textNative = lView[adjustedIndex] = createTextNode(lView[RENDERER], value);
appendChild(tView, lView, textNative, tNode);
// Text nodes are self closing.
File diff suppressed because it is too large Load Diff
+82 -2
View File
@@ -7,6 +7,7 @@
*/
import {KeyValueArray} from '../../util/array_utils';
import {TStylingRange} from '../interfaces/styling';
import {TIcu} from './i18n';
import {CssSelector} from './projection';
import {RNode} from './renderer';
import {LView, TView} from './view';
@@ -16,9 +17,11 @@ import {LView, TView} from './view';
* TNodeType corresponds to the {@link TNode} `type` property.
*/
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 an {@link LContainer} for embedded views.
*/
// FIXME(misko): Verify that we still need a `Container`, at the very least update the text.
Container = 0,
/**
* The TNode contains information about an `<ng-content>` projection
@@ -36,6 +39,20 @@ export const enum TNodeType {
* The TNode contains information about an ICU comment used in `i18n`.
*/
IcuContainer = 4,
/**
* Special node type representing a placeholder for future `TNode` at this location.
*
* I18n translation blocks are created before the element nodes which they contain. (I18n blocks
* can span over many elements.) Because i18n `TNode`s (representing text) are created first they
* often may need to point to element `TNode`s which are not yet created. In such a case we create
* a `Placeholder` `TNode`. This allows the i18n to structurally link the `TNode`s together
* without knowing any information about the future nodes which will be at that location.
*
* On `firstCreatePass` When element instruction executes it will try to create a `TNode` at that
* 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.
*/
Placeholder = 5,
}
/**
@@ -47,7 +64,8 @@ export const TNodeTypeAsString = [
'Projection', // 1
'Element', // 2
'ElementContainer', // 3
'IcuContainer' // 4
'IcuContainer', // 4
'Placeholder', // 5
] as const;
@@ -293,6 +311,59 @@ export interface TNode {
*/
index: number;
/**
* Insert before existing DOM node index.
*
* When DOM nodes are being inserted, normally they are being appended as they are created.
* Under i18n case, the translated text nodes are created ahead of time as part of the
* `ɵɵi18nStart` instruction which means that this `TNode` can't just be appended and instead
* needs to be inserted using `insertBeforeIndex` semantics.
*
* Additionally sometimes it is necessary to insert new text nodes as a child of this `TNode`. In
* such a case the value stores an array of text nodes to insert.
*
* Example:
* ```
* <div i18n>
* Hello <span>World</span>!
* </div>
* ```
* In the above example the `ɵɵi18nStart` instruction can create `Hello `, `World` and `!` text
* 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
* `<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.
*
* Pseudo code:
* ```
* if (insertBeforeIndex === null) {
* // append as normal
* } else if (Array.isArray(insertBeforeIndex)) {
* // First insert current `TNode` at correct location
* const currentNode = lView[this.index];
* parentNode.insertBefore(currentNode, lView[this.insertBeforeIndex[0]]);
* // Now append all of the children
* for(let i=1; i<this.insertBeforeIndex; i++) {
* currentNode.appendChild(lView[this.insertBeforeIndex[i]]);
* }
* } else {
* parentNode.insertBefore(lView[this.index], lView[this.insertBeforeIndex])
* }
* ```
* - null: Append as normal using `parentNode.appendChild`
* - `number`: Append using
* `parentNode.insertBefore(lView[this.index], lView[this.insertBeforeIndex])`
*
* *Initialization*
*
* Because `ɵɵi18nStart` executes before nodes are created, on `TView.firstCreatePass` it is not
* possible for `ɵɵi18nStart` to set the `insertBeforeIndex` value as the corresponding `TNode`
* has not yet been created. For this reason the `ɵɵi18nStart` creates a `TNodeType.Placeholder`
* `TNode` at that location. See `TNodeType.Placeholder` for more information.
*/
insertBeforeIndex: InsertBeforeIndex;
/**
* The index of the closest injector in this node's LView.
*
@@ -357,6 +428,8 @@ export interface TNode {
providerIndexes: TNodeProviderIndexes;
/** The tag name associated with this node. */
// FIXME(misko): rename to `value` and change the type to `any` so that
// subclasses of `TNode` can use it to link additional payload
tagName: string|null;
/**
@@ -643,6 +716,11 @@ export interface TNode {
styleBindings: TStylingRange;
}
/**
* See `TNode.insertBeforeIndex`
*/
export type InsertBeforeIndex = null|number|number[];
/** Static data for an element */
export interface TElementNode extends TNode {
/** Index in the data[] array */
@@ -715,10 +793,12 @@ export interface TElementContainerNode extends TNode {
export interface TIcuContainerNode extends TNode {
/** Index in the LView[] array. */
index: number;
child: TElementNode|TTextNode|null;
child: null;
parent: TElementNode|TElementContainerNode|null;
tViews: null;
projection: null;
// FIXME(misko): Refactor to enable the next line
// tagName: TIcu;
}
/** Static data for an LProjectionNode */
@@ -74,7 +74,7 @@ export interface ProceduralRenderer3 {
*/
destroyNode?: ((node: RNode) => void)|null;
appendChild(parent: RElement, newChild: RNode): void;
insertBefore(parent: RNode, newChild: RNode, refChild: RNode|null): void;
insertBefore(parent: RNode, newChild: RNode, refChild: RNode|null, isMove?: boolean): void;
removeChild(parent: RElement, oldChild: RNode, isHostElement?: boolean): void;
selectRootElement(selectorOrNode: string|any, preserveContent?: boolean): RElement;
+13 -7
View File
@@ -14,7 +14,7 @@ import {Sanitizer} from '../../sanitization/sanitizer';
import {LContainer} from './container';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList, ViewQueriesFunction} from './definition';
import {I18nUpdateOpCodes, TI18n} from './i18n';
import {I18nUpdateOpCodes, TI18n, TIcu} from './i18n';
import {TConstants, TNode, TNodeTypeAsString} from './node';
import {PlayerHandler} from './player';
import {LQueries, TQueries} from './query';
@@ -839,7 +839,7 @@ export type DestroyHookData = (HookEntry|HookData)[];
*/
export type TData =
(TNode|PipeDef<any>|DirectiveDef<any>|ComponentDef<any>|number|TStylingRange|TStylingKey|
Type<any>|InjectionToken<any>|TI18n|I18nUpdateOpCodes|null|string)[];
Type<any>|InjectionToken<any>|TI18n|I18nUpdateOpCodes|TIcu|null|string)[];
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.
@@ -872,6 +872,11 @@ export interface LViewDebug {
indexWithinInitPhase: number,
};
/**
* Associated TView
*/
readonly tView: TView;
/**
* Parent view (or container)
*/
@@ -894,6 +899,12 @@ export interface LViewDebug {
*/
readonly nodes: DebugNode[];
/**
* Template structure (no instance data).
* (Shows how TNodes are connected)
*/
readonly template: string;
/**
* HTML representation of the `LView`.
*
@@ -921,11 +932,6 @@ export interface LViewDebug {
*/
readonly vars: LViewDebugRange;
/**
* Sub range of `LView` containing i18n (translated DOM elements).
*/
readonly i18n: LViewDebugRange;
/**
* Sub range of `LView` containing expando (used by DI).
*/
File diff suppressed because it is too large Load Diff
+72 -21
View File
@@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {assertDefined, assertEqual} from '../util/assert';
import {assertDefined, assertEqual, assertNotEqual} from '../util/assert';
import {assertLViewOrUndefined, assertTNodeForTView} from './assert';
import {DirectiveDef} from './interfaces/definition';
import {TNode} from './interfaces/node';
import {TNode, TNodeType} from './interfaces/node';
import {CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState, TData, TVIEW, TView} from './interfaces/view';
import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces';
import {getTNode} from './util/view_utils';
@@ -116,6 +116,22 @@ interface LFrame {
* `LView[currentDirectiveIndex]` is directive instance.
*/
currentDirectiveIndex: number;
/**
* Are we currently in i18n block as denoted by `ɵɵelementStart` and `ɵɵelementEnd`.
*
* This information is needed because while we are in i18n block all elements must be pre-declared
* in the translation. (i.e. `Hello �#2�World�/#2�!` pre-declares element at `�#2�` location.)
* This allocates `TNodeType.Placeholder` element at location `2`. If translator removes `�#2�`
* from translation than the runtime must also ensure tha element at `2` does not get inserted
* into the DOM. The translation does not carry information about deleted elements. Therefor the
* only way to know that an element is deleted is that it was not pre-declared in the translation.
*
* This flag works by ensuring that elements which are created without pre-declaration
* (`TNodeType.Placeholder`) are not inserted into the DOM render tree. (It does mean that the
* element still gets instantiated along with all of its behavior [directives])
*/
inI18n: boolean;
}
/**
@@ -166,12 +182,21 @@ interface InstructionState {
isInCheckNoChangesMode: boolean;
}
export const instructionState: InstructionState = {
const instructionState: InstructionState = {
lFrame: createLFrame(null),
bindingsEnabled: true,
isInCheckNoChangesMode: false,
};
/**
* Returns true if the instruction state stack is empty.
*
* Intended to be called from tests only (tree shaken otherwise).
*/
export function specOnlyIsInstructionStateEmpty(): boolean {
return instructionState.lFrame.parent === null;
}
export function getElementDepthCount() {
return instructionState.lFrame.elementDepthCount;
@@ -265,14 +290,30 @@ export function ɵɵrestoreView(viewToRestore: OpaqueViewState) {
instructionState.lFrame.contextLView = viewToRestore as any as LView;
}
export function getCurrentTNode(): TNode|null {
let currentTNode = getCurrentTNodePlaceholderOk();
while (currentTNode !== null && currentTNode.type === TNodeType.Placeholder) {
currentTNode = currentTNode.parent;
}
return currentTNode;
}
export function getCurrentTNodePlaceholderOk(): TNode|null {
return instructionState.lFrame.currentTNode;
}
export function setCurrentTNode(tNode: TNode, isParent: boolean) {
ngDevMode && assertTNodeForTView(tNode, instructionState.lFrame.tView);
instructionState.lFrame.currentTNode = tNode;
instructionState.lFrame.isParent = isParent;
export function getCurrentParentTNode(): TNode|null {
const lFrame = instructionState.lFrame;
const currentTNode = lFrame.currentTNode;
return lFrame.isParent ? currentTNode : currentTNode!.parent;
}
export function setCurrentTNode(tNode: TNode|null, isParent: boolean) {
ngDevMode && tNode && assertTNodeForTView(tNode, instructionState.lFrame.tView);
const lFrame = instructionState.lFrame;
lFrame.currentTNode = tNode;
lFrame.isParent = isParent;
}
export function isCurrentTNodeParent(): boolean {
@@ -328,6 +369,14 @@ export function incrementBindingIndex(count: number): number {
return index;
}
export function isInI18nBlock() {
return instructionState.lFrame.inI18n;
}
export function setInI18nBlock(isInI18nBlock: boolean): void {
instructionState.lFrame.inI18n = isInI18nBlock;
}
/**
* Set a new binding root index so that host template functions can execute.
*
@@ -429,6 +478,7 @@ export function enterView(newView: LView): void {
newLFrame.tView = tView;
newLFrame.contextLView = newView!;
newLFrame.bindingIndex = tView.bindingStartIndex;
newLFrame.inI18n = false;
}
/**
@@ -443,20 +493,21 @@ function allocLFrame() {
function createLFrame(parent: LFrame|null): LFrame {
const lFrame: LFrame = {
currentTNode: null, //
isParent: true, //
lView: null!, //
tView: null!, //
selectedIndex: 0, //
contextLView: null!, //
elementDepthCount: 0, //
currentNamespace: null, //
currentDirectiveIndex: -1, //
bindingRootIndex: -1, //
bindingIndex: -1, //
currentQueryIndex: 0, //
parent: parent!, //
child: null, //
currentTNode: null,
isParent: true,
lView: null!,
tView: null!,
selectedIndex: 0,
contextLView: null!,
elementDepthCount: 0,
currentNamespace: null,
currentDirectiveIndex: -1,
bindingRootIndex: -1,
bindingIndex: -1,
currentQueryIndex: 0,
parent: parent!,
child: null,
inI18n: false,
};
parent !== null && (parent.child = lFrame); // link the new LFrame for reuse.
return lFrame;
+7 -4
View File
@@ -7,10 +7,10 @@
*/
import {assertDefined, assertDomNode, assertGreaterThan, assertIndexInRange, assertLessThan} from '../../util/assert';
import {assertTNodeForLView} from '../assert';
import {assertTNode, assertTNodeForLView} from '../assert';
import {LContainer, TYPE} from '../interfaces/container';
import {LContext, MONKEY_PATCH_KEY_NAME} from '../interfaces/context';
import {TConstants, TNode, TNodeType} from '../interfaces/node';
import {TConstants, TNode} from '../interfaces/node';
import {isProceduralRenderer, RNode} from '../interfaces/renderer';
import {isLContainer, isLView} from '../interfaces/type_checks';
import {FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, PREORDER_HOOK_FLAGS, RENDERER, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TView} from '../interfaces/view';
@@ -117,10 +117,13 @@ export function getNativeByTNodeOrNull(tNode: TNode|null, lView: LView): RNode|n
}
// fixme(misko): The return Type should be `TNode|null`
export function getTNode(tView: TView, index: number): TNode {
ngDevMode && assertGreaterThan(index, -1, 'wrong index for TNode');
ngDevMode && assertLessThan(index, tView.data.length, 'wrong index for TNode');
return tView.data[index + HEADER_OFFSET] as TNode;
ngDevMode && assertLessThan(index, tView.data.length - HEADER_OFFSET, 'wrong index for TNode');
const tNode = tView.data[index + HEADER_OFFSET] as TNode;
ngDevMode && tNode !== null && assertTNode(tNode);
return tNode;
}
/** Retrieves a value from any `LView` or `TData`. */
@@ -20,7 +20,7 @@ import {assertDefined, assertEqual, assertGreaterThan, assertLessThan} from '../
import {assertLContainer, assertNodeInjector} from './assert';
import {getParentInjectorLocation, NodeInjector} from './di';
import {addToViewTree, createLContainer, createLView, renderView} from './instructions/shared';
import {addToViewTree, createLContainer, createLView, createTNode, renderView} from './instructions/shared';
import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE, VIEW_REFS} from './interfaces/container';
import {NodeInjectorOffset} from './interfaces/injector';
import {TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNode, TNodeType} from './interfaces/node';
@@ -287,9 +287,9 @@ export function createContainerRef(
// Physical operation of adding the DOM nodes.
const beforeNode = getBeforeNodeForView(adjustedIdx, lContainer);
const renderer = lView[RENDERER];
const renderParent = nativeParentNode(renderer, lContainer[NATIVE] as RElement | RComment);
if (renderParent !== null) {
addViewToContainer(tView, lContainer[T_HOST], renderer, lView, renderParent, beforeNode);
const parentRNode = nativeParentNode(renderer, lContainer[NATIVE] as RElement | RComment);
if (parentRNode !== null) {
addViewToContainer(tView, lContainer[T_HOST], renderer, lView, parentRNode, beforeNode);
}
(viewRef as ViewRef<any>).attachToViewContainerRef(this);
@@ -388,9 +388,14 @@ export function createContainerRef(
const hostNative = getNativeByTNode(hostTNode, hostView)!;
const parentOfHostNative = nativeParentNode(renderer, hostNative);
nativeInsertBefore(
renderer, parentOfHostNative!, commentNode, nativeNextSibling(renderer, hostNative));
renderer, parentOfHostNative!, commentNode, nativeNextSibling(renderer, hostNative),
false);
} else {
appendChild(hostView[TVIEW], hostView, commentNode, hostTNode);
// The TNode created here is bogus, in that it is not added to the TView. It is only created
// to allow us to create a dynamic Comment node.
const commentTNode = createTNode(
hostView[TVIEW], hostTNode.parent, TNodeType.Container, hostTNode.type, null, null);
appendChild(hostView[TVIEW], hostView, commentNode, commentTNode);
}
}

Some files were not shown because too many files have changed in this diff Show More