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:
committed by
Alex Rickabaugh
parent
6790848f68
commit
ca11ef2376
@@ -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",
|
||||
|
||||
Vendored
+1
-1
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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,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
Reference in New Issue
Block a user