parent
cb31381734
commit
84272e2227
|
@ -87,6 +87,13 @@ export {
|
|||
DirectiveDefInternal as ɵDirectiveDef,
|
||||
PipeDef as ɵPipeDef,
|
||||
whenRendered as ɵwhenRendered,
|
||||
iA as ɵiA,
|
||||
iEM as ɵiEM,
|
||||
iI as ɵiI,
|
||||
iIV as ɵIV,
|
||||
iM as ɵiM,
|
||||
I18nInstruction as ɵI18nInstruction,
|
||||
I18nExpInstruction as ɵI18nExpInstruction,
|
||||
} from './render3/index';
|
||||
export {
|
||||
bypassSanitizationTrustHtml as ɵbypassSanitizationTrustHtml,
|
||||
|
|
|
@ -0,0 +1,460 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {assertEqual, assertLessThan} from './assert';
|
||||
import {NO_CHANGE, bindingUpdated, createLNode, getPreviousOrParentNode, getRenderer, getViewData, load} from './instructions';
|
||||
import {LContainerNode, LElementNode, LNode, TNodeType} from './interfaces/node';
|
||||
import {BINDING_INDEX} from './interfaces/view';
|
||||
import {appendChild, createTextNode, getParentLNode, removeChild} from './node_manipulation';
|
||||
import {stringify} from './util';
|
||||
|
||||
/**
|
||||
* A list of flags to encode the i18n instructions used to translate the template.
|
||||
* We shift the flags by 29 so that 30 & 31 & 32 bits contains the instructions.
|
||||
*/
|
||||
export const enum I18nInstructions {
|
||||
Text = 1 << 29,
|
||||
Element = 2 << 29,
|
||||
Expression = 3 << 29,
|
||||
CloseNode = 4 << 29,
|
||||
RemoveNode = 5 << 29,
|
||||
/** Used to decode the number encoded with the instruction. */
|
||||
IndexMask = (1 << 29) - 1,
|
||||
/** Used to test the type of instruction. */
|
||||
InstructionMask = ~((1 << 29) - 1),
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the instructions used to translate the template.
|
||||
* Instructions can be a placeholder index, a static text or a simple bit field (`I18nFlag`).
|
||||
* When the instruction is the flag `Text`, it is always followed by its text value.
|
||||
*/
|
||||
export type I18nInstruction = number | string;
|
||||
/**
|
||||
* Represents the instructions used to translate attributes containing expressions.
|
||||
* Even indexes contain static strings, while odd indexes contain the index of the expression whose
|
||||
* value will be concatenated into the final translation.
|
||||
*/
|
||||
export type I18nExpInstruction = number | string;
|
||||
/** Mapping of placeholder names to their absolute indexes in their templates. */
|
||||
export type PlaceholderMap = {
|
||||
[name: string]: number
|
||||
};
|
||||
const i18nTagRegex = /\{\$([^}]+)\}/g;
|
||||
|
||||
/**
|
||||
* Takes a translation string, the initial list of placeholders (elements and expressions) and the
|
||||
* indexes of their corresponding expression nodes to return a list of instructions for each
|
||||
* template function.
|
||||
*
|
||||
* Because embedded templates have different indexes for each placeholder, each parameter (except
|
||||
* the translation) is an array, where each value corresponds to a different template, by order of
|
||||
* appearance.
|
||||
*
|
||||
* @param translation A translation string where placeholders are represented by `{$name}`
|
||||
* @param elements An array containing, for each template, the maps of element placeholders and
|
||||
* their indexes.
|
||||
* @param expressions An array containing, for each template, the maps of expression placeholders
|
||||
* and their indexes.
|
||||
* @param tmplContainers An array of template container placeholders whose content should be ignored
|
||||
* when generating the instructions for their parent template.
|
||||
* @param lastChildIndex The index of the last child of the i18n node. Used when the i18n block is
|
||||
* an ng-container.
|
||||
*
|
||||
* @returns A list of instructions used to translate each template.
|
||||
*/
|
||||
export function i18nMapping(
|
||||
translation: string, elements: (PlaceholderMap | null)[] | null,
|
||||
expressions?: (PlaceholderMap | null)[] | null, tmplContainers?: string[] | null,
|
||||
lastChildIndex?: number | null): I18nInstruction[][] {
|
||||
const translationParts = translation.split(i18nTagRegex);
|
||||
const instructions: I18nInstruction[][] = [];
|
||||
|
||||
generateMappingInstructions(
|
||||
0, translationParts, instructions, elements, expressions, tmplContainers, lastChildIndex);
|
||||
|
||||
return instructions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function that reads the translation parts and generates a set of instructions for each
|
||||
* template.
|
||||
*
|
||||
* See `i18nMapping()` for more details.
|
||||
*
|
||||
* @param index The current index in `translationParts`.
|
||||
* @param translationParts The translation string split into an array of placeholders and text
|
||||
* elements.
|
||||
* @param instructions The current list of instructions to update.
|
||||
* @param elements An array containing, for each template, the maps of element placeholders and
|
||||
* their indexes.
|
||||
* @param expressions An array containing, for each template, the maps of expression placeholders
|
||||
* and their indexes.
|
||||
* @param tmplContainers An array of template container placeholders whose content should be ignored
|
||||
* when generating the instructions for their parent template.
|
||||
* @param lastChildIndex The index of the last child of the i18n node. Used when the i18n block is
|
||||
* an ng-container.
|
||||
* @returns the current index in `translationParts`
|
||||
*/
|
||||
function generateMappingInstructions(
|
||||
index: number, translationParts: string[], instructions: I18nInstruction[][],
|
||||
elements: (PlaceholderMap | null)[] | null, expressions?: (PlaceholderMap | null)[] | null,
|
||||
tmplContainers?: string[] | null, lastChildIndex?: number | null): number {
|
||||
const tmplIndex = instructions.length;
|
||||
const tmplInstructions: I18nInstruction[] = [];
|
||||
const phVisited = [];
|
||||
let openedTagCount = 0;
|
||||
let maxIndex = 0;
|
||||
|
||||
instructions.push(tmplInstructions);
|
||||
|
||||
for (; index < translationParts.length; index++) {
|
||||
const value = translationParts[index];
|
||||
|
||||
// Odd indexes are placeholders
|
||||
if (index & 1) {
|
||||
let phIndex;
|
||||
|
||||
if (elements && elements[tmplIndex] &&
|
||||
typeof(phIndex = elements[tmplIndex] ![value]) !== 'undefined') {
|
||||
// The placeholder represents a DOM element
|
||||
// Add an instruction to move the element
|
||||
tmplInstructions.push(phIndex | I18nInstructions.Element);
|
||||
phVisited.push(value);
|
||||
openedTagCount++;
|
||||
} else if (
|
||||
expressions && expressions[tmplIndex] &&
|
||||
typeof(phIndex = expressions[tmplIndex] ![value]) !== 'undefined') {
|
||||
// The placeholder represents an expression
|
||||
// Add an instruction to move the expression
|
||||
tmplInstructions.push(phIndex | I18nInstructions.Expression);
|
||||
phVisited.push(value);
|
||||
} else { // It is a closing tag
|
||||
tmplInstructions.push(I18nInstructions.CloseNode);
|
||||
|
||||
if (tmplIndex > 0) {
|
||||
openedTagCount--;
|
||||
|
||||
// If we have reached the closing tag for this template, exit the loop
|
||||
if (openedTagCount === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof phIndex !== 'undefined' && phIndex > maxIndex) {
|
||||
maxIndex = phIndex;
|
||||
}
|
||||
|
||||
if (tmplContainers && tmplContainers.indexOf(value) !== -1 &&
|
||||
tmplContainers.indexOf(value) >= tmplIndex) {
|
||||
index = generateMappingInstructions(
|
||||
index, translationParts, instructions, elements, expressions, tmplContainers,
|
||||
lastChildIndex);
|
||||
}
|
||||
|
||||
} else if (value) {
|
||||
// It's a non-empty string, create a text node
|
||||
tmplInstructions.push(I18nInstructions.Text, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if some elements from the template are missing from the translation
|
||||
if (elements) {
|
||||
const tmplElements = elements[tmplIndex];
|
||||
|
||||
if (tmplElements) {
|
||||
const phKeys = Object.keys(tmplElements);
|
||||
|
||||
for (let i = 0; i < phKeys.length; i++) {
|
||||
const ph = phKeys[i];
|
||||
|
||||
if (phVisited.indexOf(ph) === -1) {
|
||||
let index = tmplElements[ph];
|
||||
// Add an instruction to remove the element
|
||||
tmplInstructions.push(index | I18nInstructions.RemoveNode);
|
||||
|
||||
if (index > maxIndex) {
|
||||
maxIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if some expressions from the template are missing from the translation
|
||||
if (expressions) {
|
||||
const tmplExpressions = expressions[tmplIndex];
|
||||
|
||||
if (tmplExpressions) {
|
||||
const phKeys = Object.keys(tmplExpressions);
|
||||
|
||||
for (let i = 0; i < phKeys.length; i++) {
|
||||
const ph = phKeys[i];
|
||||
|
||||
if (phVisited.indexOf(ph) === -1) {
|
||||
let index = tmplExpressions[ph];
|
||||
if (ngDevMode) {
|
||||
assertLessThan(
|
||||
index.toString(2).length, 28, `Index ${index} is too big and will overflow`);
|
||||
}
|
||||
// Add an instruction to remove the expression
|
||||
tmplInstructions.push(index | I18nInstructions.RemoveNode);
|
||||
|
||||
if (index > maxIndex) {
|
||||
maxIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tmplIndex === 0 && typeof lastChildIndex === 'number') {
|
||||
// The current parent is an ng-container and it has more children after the translation that we
|
||||
// need to append to keep the order of the DOM nodes correct
|
||||
for (let i = maxIndex + 1; i <= lastChildIndex; i++) {
|
||||
if (ngDevMode) {
|
||||
assertLessThan(i.toString(2).length, 28, `Index ${i} is too big and will overflow`);
|
||||
}
|
||||
// We consider those additional placeholders as expressions because we don't care about
|
||||
// their children, all we need to do is to append them
|
||||
tmplInstructions.push(i | I18nInstructions.Expression);
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
function appendI18nNode(node: LNode, parentNode: LNode, previousNode: LNode) {
|
||||
if (ngDevMode) {
|
||||
ngDevMode.rendererMoveNode++;
|
||||
}
|
||||
|
||||
const viewData = getViewData();
|
||||
|
||||
appendChild(parentNode, node.native || null, viewData);
|
||||
|
||||
if (previousNode === parentNode && parentNode.pChild === null) {
|
||||
parentNode.pChild = node;
|
||||
} else {
|
||||
previousNode.pNextOrParent = node;
|
||||
}
|
||||
|
||||
// Template containers also have a comment node for the `ViewContainerRef` that should be moved
|
||||
if (node.tNode.type === TNodeType.Container && node.dynamicLContainerNode) {
|
||||
// (node.native as RComment).textContent = 'test';
|
||||
// console.log(node.native);
|
||||
appendChild(parentNode, node.dynamicLContainerNode.native || null, viewData);
|
||||
node.pNextOrParent = node.dynamicLContainerNode;
|
||||
return node.dynamicLContainerNode;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a list of instructions generated by `i18nMapping()` to transform the template accordingly.
|
||||
*
|
||||
* @param startIndex Index of the first element to translate (for instance the first child of the
|
||||
* element with the i18n attribute).
|
||||
* @param instructions The list of instructions to apply on the current view.
|
||||
*/
|
||||
export function i18nApply(startIndex: number, instructions: I18nInstruction[]): void {
|
||||
const viewData = getViewData();
|
||||
if (ngDevMode) {
|
||||
assertEqual(viewData[BINDING_INDEX], -1, 'i18nApply should be called before any binding');
|
||||
}
|
||||
|
||||
if (!instructions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const renderer = getRenderer();
|
||||
let localParentNode: LNode = getParentLNode(load(startIndex)) || getPreviousOrParentNode();
|
||||
let localPreviousNode: LNode = localParentNode;
|
||||
|
||||
for (let i = 0; i < instructions.length; i++) {
|
||||
const instruction = instructions[i] as number;
|
||||
switch (instruction & I18nInstructions.InstructionMask) {
|
||||
case I18nInstructions.Element:
|
||||
const element: LNode = load(instruction & I18nInstructions.IndexMask);
|
||||
localPreviousNode = appendI18nNode(element, localParentNode, localPreviousNode);
|
||||
localParentNode = element;
|
||||
break;
|
||||
case I18nInstructions.Expression:
|
||||
const expr: LNode = load(instruction & I18nInstructions.IndexMask);
|
||||
localPreviousNode = appendI18nNode(expr, localParentNode, localPreviousNode);
|
||||
break;
|
||||
case I18nInstructions.Text:
|
||||
if (ngDevMode) {
|
||||
ngDevMode.rendererCreateTextNode++;
|
||||
}
|
||||
const value = instructions[++i];
|
||||
const textRNode = createTextNode(value, renderer);
|
||||
// If we were to only create a `RNode` then projections won't move the text.
|
||||
// But since this text doesn't have an index in `LViewData`, we need to create an
|
||||
// `LElementNode` with the index -1 so that it isn't saved in `LViewData`
|
||||
const textLNode = createLNode(-1, TNodeType.Element, textRNode, null, null);
|
||||
textLNode.dynamicParent = localParentNode as LElementNode | LContainerNode;
|
||||
localPreviousNode = appendI18nNode(textLNode, localParentNode, localPreviousNode);
|
||||
break;
|
||||
case I18nInstructions.CloseNode:
|
||||
localPreviousNode = localParentNode;
|
||||
localParentNode = getParentLNode(localParentNode) !;
|
||||
break;
|
||||
case I18nInstructions.RemoveNode:
|
||||
if (ngDevMode) {
|
||||
ngDevMode.rendererRemoveNode++;
|
||||
}
|
||||
const index = instruction & I18nInstructions.IndexMask;
|
||||
const removedNode: LNode|LContainerNode = load(index);
|
||||
const parentNode = getParentLNode(removedNode) !;
|
||||
removeChild(parentNode, removedNode.native || null, viewData);
|
||||
|
||||
// For template containers we also need to remove their `ViewContainerRef` from the DOM
|
||||
if (removedNode.tNode.type === TNodeType.Container && removedNode.dynamicLContainerNode) {
|
||||
removeChild(parentNode, removedNode.dynamicLContainerNode.native || null, viewData);
|
||||
removedNode.dynamicLContainerNode.tNode.detached = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a translation string and the initial list of expressions and returns a list of instructions
|
||||
* that will be used to translate an attribute.
|
||||
* Even indexes contain static strings, while odd indexes contain the index of the expression whose
|
||||
* value will be concatenated into the final translation.
|
||||
*/
|
||||
export function i18nExpMapping(
|
||||
translation: string, placeholders: PlaceholderMap): I18nExpInstruction[] {
|
||||
const staticText: I18nExpInstruction[] = translation.split(i18nTagRegex);
|
||||
// odd indexes are placeholders
|
||||
for (let i = 1; i < staticText.length; i += 2) {
|
||||
staticText[i] = placeholders[staticText[i]];
|
||||
}
|
||||
return staticText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value of up to 8 expressions have changed and replaces them by their values in a
|
||||
* translation, or returns NO_CHANGE.
|
||||
*
|
||||
* @returns The concatenated string when any of the arguments changes, `NO_CHANGE` otherwise.
|
||||
*/
|
||||
export function i18nInterpolation(
|
||||
instructions: I18nExpInstruction[], numberOfExp: number, v0: any, v1?: any, v2?: any, v3?: any,
|
||||
v4?: any, v5?: any, v6?: any, v7?: any): string|NO_CHANGE {
|
||||
let different = bindingUpdated(v0);
|
||||
|
||||
if (numberOfExp > 1) {
|
||||
different = bindingUpdated(v1) || different;
|
||||
|
||||
if (numberOfExp > 2) {
|
||||
different = bindingUpdated(v2) || different;
|
||||
|
||||
if (numberOfExp > 3) {
|
||||
different = bindingUpdated(v3) || different;
|
||||
|
||||
if (numberOfExp > 4) {
|
||||
different = bindingUpdated(v4) || different;
|
||||
|
||||
if (numberOfExp > 5) {
|
||||
different = bindingUpdated(v5) || different;
|
||||
|
||||
if (numberOfExp > 6) {
|
||||
different = bindingUpdated(v6) || different;
|
||||
|
||||
if (numberOfExp > 7) {
|
||||
different = bindingUpdated(v7) || different;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!different) {
|
||||
return NO_CHANGE;
|
||||
}
|
||||
|
||||
let res = '';
|
||||
for (let i = 0; i < instructions.length; i++) {
|
||||
let value: any;
|
||||
// Odd indexes are placeholders
|
||||
if (i & 1) {
|
||||
switch (instructions[i]) {
|
||||
case 0:
|
||||
value = v0;
|
||||
break;
|
||||
case 1:
|
||||
value = v1;
|
||||
break;
|
||||
case 2:
|
||||
value = v2;
|
||||
break;
|
||||
case 3:
|
||||
value = v3;
|
||||
break;
|
||||
case 4:
|
||||
value = v4;
|
||||
break;
|
||||
case 5:
|
||||
value = v5;
|
||||
break;
|
||||
case 6:
|
||||
value = v6;
|
||||
break;
|
||||
case 7:
|
||||
value = v7;
|
||||
break;
|
||||
}
|
||||
|
||||
res += stringify(value);
|
||||
} else {
|
||||
res += instructions[i];
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a translated interpolation binding with a variable number of expressions.
|
||||
*
|
||||
* If there are 1 to 8 expressions then `i18nInterpolation()` should be used instead. It is faster
|
||||
* because there is no need to create an array of expressions and iterate over it.
|
||||
*
|
||||
* @returns The concatenated string when any of the arguments changes, `NO_CHANGE` otherwise.
|
||||
*/
|
||||
export function i18nInterpolationV(instructions: I18nExpInstruction[], values: any[]): string|
|
||||
NO_CHANGE {
|
||||
let different = false;
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
// Check if bindings have changed
|
||||
bindingUpdated(values[i]) && (different = true);
|
||||
}
|
||||
|
||||
if (!different) {
|
||||
return NO_CHANGE;
|
||||
}
|
||||
|
||||
let res = '';
|
||||
for (let i = 0; i < instructions.length; i++) {
|
||||
// Odd indexes are placeholders
|
||||
if (i & 1) {
|
||||
res += stringify(values[instructions[i] as number]);
|
||||
} else {
|
||||
res += instructions[i];
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
import {LifecycleHooksFeature, getHostElement, getRenderedText, renderComponent, whenRendered} from './component';
|
||||
import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, defineNgModule, definePipe} from './definition';
|
||||
import {I18nExpInstruction, I18nInstruction, i18nExpMapping, i18nInterpolation, i18nInterpolationV} from './i18n';
|
||||
import {ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition';
|
||||
|
||||
export {ComponentFactory, ComponentFactoryResolver, ComponentRef} from './component_ref';
|
||||
export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, injectAttribute, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
|
||||
export {RenderFlags} from './interfaces/definition';
|
||||
|
@ -79,6 +79,16 @@ export {
|
|||
tick,
|
||||
} from './instructions';
|
||||
|
||||
export {
|
||||
i18nApply as iA,
|
||||
i18nMapping as iM,
|
||||
i18nInterpolation as iI,
|
||||
i18nInterpolationV as iIV,
|
||||
i18nExpMapping as iEM,
|
||||
I18nInstruction,
|
||||
I18nExpInstruction
|
||||
} from './i18n';
|
||||
|
||||
export {NgModuleDef, NgModuleFactory, NgModuleRef, NgModuleType} from './ng_module_ref';
|
||||
|
||||
export {
|
||||
|
@ -96,7 +106,6 @@ export {
|
|||
|
||||
export {
|
||||
QueryList,
|
||||
|
||||
query as Q,
|
||||
queryRefresh as qR,
|
||||
} from './query';
|
||||
|
|
|
@ -6,22 +6,19 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import './ng_dev_mode';
|
||||
|
||||
import {Sanitizer} from '../sanitization/security';
|
||||
|
||||
import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotEqual, assertSame} from './assert';
|
||||
import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotEqual} from './assert';
|
||||
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
|
||||
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
|
||||
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
|
||||
import {LInjector} from './interfaces/injector';
|
||||
import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, PropertyAliases, PropertyAliasValue, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType,} from './interfaces/node';
|
||||
import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
|
||||
import {LQueries} from './interfaces/query';
|
||||
import {BINDING_INDEX, CLEANUP, CONTEXT, CurrentMatchesList, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView,} from './interfaces/view';
|
||||
|
||||
import {AttributeMarker, TAttributes, LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue, TElementNode,} from './interfaces/node';
|
||||
import {BINDING_INDEX, CLEANUP, CONTEXT, CurrentMatchesList, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TVIEW, TView,} from './interfaces/view';
|
||||
import './ng_dev_mode';
|
||||
import {assertNodeType} from './node_assert';
|
||||
import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode, getLViewChild} from './node_manipulation';
|
||||
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeChild, removeView} from './node_manipulation';
|
||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||
import {ComponentDefInternal, ComponentTemplate, ComponentQuery, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
||||
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||
|
@ -103,6 +100,11 @@ export function getCurrentSanitizer(): Sanitizer|null {
|
|||
return viewData && viewData[SANITIZER];
|
||||
}
|
||||
|
||||
export function getViewData(): LViewData {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return viewData;
|
||||
}
|
||||
|
||||
/** Used to set the parent property when nodes are created. */
|
||||
let previousOrParentNode: LNode;
|
||||
|
||||
|
@ -319,7 +321,8 @@ export function createLNodeObject(
|
|||
tNode: null !,
|
||||
pNextOrParent: null,
|
||||
dynamicLContainerNode: null,
|
||||
dynamicParent: null
|
||||
dynamicParent: null,
|
||||
pChild: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -367,8 +370,8 @@ export function createLNode(
|
|||
if (index === -1 || type === TNodeType.View) {
|
||||
// View nodes are not stored in data because they can be added / removed at runtime (which
|
||||
// would cause indices to change). Their TNodes are instead stored in TView.node.
|
||||
node.tNode =
|
||||
(state as LViewData)[TVIEW].node || createTNode(type, index, null, null, tParent, null);
|
||||
node.tNode = (state ? (state as LViewData)[TVIEW].node : null) ||
|
||||
createTNode(type, index, null, null, tParent, null);
|
||||
} else {
|
||||
const adjustedIndex = index + HEADER_OFFSET;
|
||||
|
||||
|
@ -1157,7 +1160,8 @@ export function createTNode(
|
|||
next: null,
|
||||
child: null,
|
||||
parent: parent,
|
||||
dynamicContainerNode: null
|
||||
dynamicContainerNode: null,
|
||||
detached: null
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1334,8 +1338,6 @@ export function elementStyle<T>(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////
|
||||
//// Text
|
||||
//////////////////////////
|
||||
|
@ -1893,7 +1895,16 @@ export function projectionDef(
|
|||
}
|
||||
|
||||
const componentNode: LElementNode = findComponentHost(viewData);
|
||||
let componentChild: LNode|null = getChildLNode(componentNode);
|
||||
let isProjectingI18nNodes = false;
|
||||
let componentChild: LNode|null;
|
||||
// for i18n translations we use pChild to point to the next child
|
||||
// TODO(kara): Remove when removing LNodes
|
||||
if (componentNode.pChild) {
|
||||
isProjectingI18nNodes = true;
|
||||
componentChild = componentNode.pChild;
|
||||
} else {
|
||||
componentChild = getChildLNode(componentNode);
|
||||
}
|
||||
|
||||
while (componentChild !== null) {
|
||||
// execute selector matching logic if and only if:
|
||||
|
@ -1906,8 +1917,12 @@ export function projectionDef(
|
|||
distributedNodes[0].push(componentChild);
|
||||
}
|
||||
|
||||
if (isProjectingI18nNodes) {
|
||||
componentChild = componentChild.pNextOrParent;
|
||||
} else {
|
||||
componentChild = getNextLNode(componentChild);
|
||||
}
|
||||
}
|
||||
|
||||
ngDevMode && assertDataNext(index + HEADER_OFFSET);
|
||||
store(index, distributedNodes);
|
||||
|
|
|
@ -100,6 +100,13 @@ export interface LNode {
|
|||
*/
|
||||
pNextOrParent: LNode|null;
|
||||
|
||||
/**
|
||||
* If this node is part of an i18n block, pointer to the first child after translation
|
||||
* If this node is not part of an i18n block, this field is null.
|
||||
*/
|
||||
// TODO(kara): Remove this when removing pNextOrParent
|
||||
pChild: LNode|null;
|
||||
|
||||
/**
|
||||
* Pointer to the corresponding TNode object, which stores static
|
||||
* data about this node.
|
||||
|
@ -342,6 +349,12 @@ export interface TNode {
|
|||
* A pointer to a TContainerNode created by directives requesting ViewContainerRef
|
||||
*/
|
||||
dynamicContainerNode: TNode|null;
|
||||
|
||||
/**
|
||||
* If this node is part of an i18n block, it indicates whether this container is part of the DOM
|
||||
* If this node is not part of an i18n block, this field is null.
|
||||
*/
|
||||
detached: boolean|null;
|
||||
}
|
||||
|
||||
/** Static data for an LElementNode */
|
||||
|
|
|
@ -27,6 +27,8 @@ declare global {
|
|||
rendererRemoveStyle: number;
|
||||
rendererDestroy: number;
|
||||
rendererDestroyNode: number;
|
||||
rendererMoveNode: number;
|
||||
rendererRemoveNode: number;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,6 +56,8 @@ export const ngDevModeResetPerfCounters: () => void =
|
|||
rendererRemoveStyle: 0,
|
||||
rendererDestroy: 0,
|
||||
rendererDestroyNode: 0,
|
||||
rendererMoveNode: 0,
|
||||
rendererRemoveNode: 0,
|
||||
};
|
||||
}
|
||||
ngDevModeResetPerfCounters();
|
||||
|
|
|
@ -29,6 +29,9 @@ export function getNextLNode(node: LNode): LNode|null {
|
|||
|
||||
/** Retrieves the first child of a given node */
|
||||
export function getChildLNode(node: LNode): LNode|null {
|
||||
if (node.pChild) {
|
||||
return node.pChild;
|
||||
}
|
||||
if (node.tNode.child) {
|
||||
const viewData = node.tNode.type === TNodeType.View ? node.data as LViewData : node.view;
|
||||
return viewData[node.tNode.child.index];
|
||||
|
@ -313,7 +316,7 @@ export function insertView(
|
|||
// If the container's renderParent is null, we know that it is a root node of its own parent view
|
||||
// and we should wait until that parent processes its nodes (otherwise, we will insert this view's
|
||||
// nodes twice - once now and once when its parent inserts its views).
|
||||
if (container.data[RENDER_PARENT] !== null) {
|
||||
if (container.data[RENDER_PARENT] !== null && !container.tNode.detached) {
|
||||
// Find the node to insert in front of
|
||||
const beforeNode =
|
||||
index + 1 < views.length ? (getChildLNode(views[index + 1]) !).native : container.native;
|
||||
|
@ -343,7 +346,9 @@ export function detachView(container: LContainerNode, removeIndex: number): LVie
|
|||
views[removeIndex - 1].data[NEXT] = viewNode.data[NEXT] as LViewData;
|
||||
}
|
||||
views.splice(removeIndex, 1);
|
||||
if (!container.tNode.detached) {
|
||||
addRemoveViewFromContainer(container, viewNode, false);
|
||||
}
|
||||
// Notify query that view has been removed
|
||||
const removedLview = viewNode.data;
|
||||
if (removedLview[QUERIES]) {
|
||||
|
@ -506,7 +511,7 @@ export function canInsertNativeNode(parent: LNode, currentView: LViewData): bool
|
|||
/**
|
||||
* Appends the `child` element to the `parent`.
|
||||
*
|
||||
* The element insertion might be delayed {@link canInsertNativeNode}
|
||||
* The element insertion might be delayed {@link canInsertNativeNode}.
|
||||
*
|
||||
* @param parent The parent to which to append the child
|
||||
* @param child The child that should be appended
|
||||
|
@ -515,7 +520,7 @@ export function canInsertNativeNode(parent: LNode, currentView: LViewData): bool
|
|||
*/
|
||||
export function appendChild(parent: LNode, child: RNode | null, currentView: LViewData): boolean {
|
||||
if (child !== null && canInsertNativeNode(parent, currentView)) {
|
||||
// We only add element if not in View or not projected.
|
||||
// We only add the element if not in View or not projected.
|
||||
const renderer = currentView[RENDERER];
|
||||
isProceduralRenderer(renderer) ? renderer.appendChild(parent.native !as RElement, child) :
|
||||
parent.native !.appendChild(child);
|
||||
|
@ -524,6 +529,25 @@ export function appendChild(parent: LNode, child: RNode | null, currentView: LVi
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the `child` element of the `parent` from the DOM.
|
||||
*
|
||||
* @param parent The parent from which to remove the child
|
||||
* @param child The child that should be removed
|
||||
* @param currentView The current LView
|
||||
* @returns Whether or not the child was removed
|
||||
*/
|
||||
export function removeChild(parent: LNode, child: RNode | null, currentView: LViewData): boolean {
|
||||
if (child !== null && canInsertNativeNode(parent, currentView)) {
|
||||
// We only remove the element if not in View or not projected.
|
||||
const renderer = currentView[RENDERER];
|
||||
isProceduralRenderer(renderer) ? renderer.removeChild(parent.native as RElement, child) :
|
||||
parent.native !.removeChild(child);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a projected node to the DOM, or in the case of a projected container,
|
||||
* appends the nodes from all of the container's active views to the DOM.
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgForOfContext} from '@angular/common';
|
||||
import {Component} from '../../../src/core';
|
||||
import * as $r3$ from '../../../src/core_render3_private_export';
|
||||
import {NgForOf} from '../common_with_def';
|
||||
|
||||
/// See: `normative.md`
|
||||
describe('i18n', () => {
|
||||
type $RenderFlags$ = $r3$.ɵRenderFlags;
|
||||
|
||||
it('should support html', () => {
|
||||
type $MyApp$ = MyApp;
|
||||
const $msg_1$ = `{$START_P}contenu{$END_P}`;
|
||||
const $i18n_1$ = $r3$.ɵiM($msg_1$, [{START_P: 1}]);
|
||||
|
||||
@Component({selector: 'my-app', template: `<div i18n><p>content</p></div>`})
|
||||
class MyApp {
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyApp,
|
||||
selectors: [['my-app']],
|
||||
factory: () => new MyApp(),
|
||||
template: function(rf: $RenderFlags$, ctx: $MyApp$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵE(1, 'p');
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵiA(1, $i18n_1$[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should support expressions', () => {
|
||||
type $MyApp$ = MyApp;
|
||||
const $msg_1$ = `contenu: {$EXP_1}`;
|
||||
const $i18n_1$ = $r3$.ɵiM($msg_1$, null, [{EXP_1: 1}]);
|
||||
|
||||
@Component({selector: 'my-app', template: `<div i18n>content: {{exp1}}</div>`})
|
||||
class MyApp {
|
||||
exp1 = '1';
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyApp,
|
||||
selectors: [['my-app']],
|
||||
factory: () => new MyApp(),
|
||||
template: function(rf: $RenderFlags$, ctx: $MyApp$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵT(1);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵiA(1, $i18n_1$[0]);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵt(3, $r3$.ɵb(ctx.exp1));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should support expressions in attributes', () => {
|
||||
type $MyApp$ = MyApp;
|
||||
const $msg_1$ = `titre: {$EXP_1}`;
|
||||
const $i18n_1$ = $r3$.ɵiEM($msg_1$, {EXP_1: 1});
|
||||
|
||||
@Component({selector: 'my-app', template: `<div i18n><p title="title: {{exp1}}"></p></div>`})
|
||||
class MyApp {
|
||||
exp1 = '1';
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyApp,
|
||||
selectors: [['my-app']],
|
||||
factory: () => new MyApp(),
|
||||
template: function(rf: $RenderFlags$, ctx: $MyApp$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵE(1, 'p');
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵp(0, 'title', $r3$.ɵiI($i18n_1$, 2, ctx.exp1));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should support embedded templates', () => {
|
||||
type $MyApp$ = MyApp;
|
||||
const $msg_1$ = `{$START_LI}valeur: {$EXP_1}!{$END_LI}`;
|
||||
const $i18n_1$ =
|
||||
$r3$.ɵiM($msg_1$, [{START_LI: 1}, {START_LI: 0}], [null, {EXP_1: 1}], ['START_LI']);
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: `<ul i18n><li *ngFor="let item of items">value: {{item}}</li></ul>`
|
||||
})
|
||||
class MyApp {
|
||||
items: string[] = ['1', '2'];
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyApp,
|
||||
factory: () => new MyApp(),
|
||||
selectors: [['my-app']],
|
||||
template: (rf: $RenderFlags$, myApp: $MyApp$) => {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'ul');
|
||||
$r3$.ɵC(1, liTemplate, null, ['ngForOf', '']);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵiA(1, $i18n_1$[0]);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵp(1, 'ngForOf', $r3$.ɵb(myApp.items));
|
||||
}
|
||||
|
||||
function liTemplate(rf1: $RenderFlags$, row: NgForOfContext<string>) {
|
||||
if (rf1 & 1) {
|
||||
$r3$.ɵE(0, 'li');
|
||||
$r3$.ɵT(1);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵiA(0, $i18n_1$[1]);
|
||||
}
|
||||
if (rf1 & 2) {
|
||||
$r3$.ɵt(1, $r3$.ɵb(row.$implicit));
|
||||
}
|
||||
}
|
||||
},
|
||||
directives: () => [NgForOf]
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -24,7 +24,8 @@ function testLStaticData(tagName: string, attrs: TAttributes | null): TNode {
|
|||
next: null,
|
||||
child: null,
|
||||
parent: null,
|
||||
dynamicContainerNode: null
|
||||
dynamicContainerNode: null,
|
||||
detached: null
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue