diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts index eee7e684ed..d193d48970 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts @@ -337,8 +337,6 @@ describe('i18n support in the template compiler', () => { `; - // TODO (FW-1942): update the code to avoid adding `title` attribute in plain form - // into the `consts` array on Component def. const output = String.raw` var $I18N_0$; if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { @@ -350,7 +348,7 @@ describe('i18n support in the template compiler', () => { } const $_c2$ = ["title", $I18N_0$]; … - consts: [["title", "Hello"]], + consts: [[${AttributeMarker.I18n}, "title"]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 0, 0, "ng-template", 0); @@ -367,8 +365,6 @@ describe('i18n support in the template compiler', () => { Test `; - // TODO (FW-1942): update the code to avoid adding `title` attribute in plain form - // into the `consts` array on Component def. const output = String.raw` var $I18N_0$; if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { @@ -391,7 +387,7 @@ describe('i18n support in the template compiler', () => { } } … - consts: [[${AttributeMarker.Template}, "ngIf"], ["title", "Hello"]], + consts: [[${AttributeMarker.Template}, "ngIf"], [${AttributeMarker.I18n}, "title"]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵtemplate(0, MyComponent_0_Template, 2, 0, undefined, 0); diff --git a/packages/compiler/src/render3/view/i18n/util.ts b/packages/compiler/src/render3/view/i18n/util.ts index c6b48ee840..06cfd840d7 100644 --- a/packages/compiler/src/render3/view/i18n/util.ts +++ b/packages/compiler/src/render3/view/i18n/util.ts @@ -9,6 +9,7 @@ import * as i18n from '../../../i18n/i18n_ast'; import {toPublicName} from '../../../i18n/serializers/xmb'; import * as html from '../../../ml_parser/ast'; import * as o from '../../../output/output_ast'; +import * as t from '../../r3_ast'; /* Closure variables holding messages must be named `MSG_[A-Z0-9]+` */ const CLOSURE_TRANSLATION_PREFIX = 'MSG_'; @@ -41,6 +42,10 @@ export function isSingleI18nIcu(meta?: i18n.I18nMeta): boolean { return isI18nRootNode(meta) && meta.nodes.length === 1 && meta.nodes[0] instanceof i18n.Icu; } +export function hasI18nMeta(node: t.Node&{i18n?: i18n.I18nMeta}): boolean { + return !!node.i18n; +} + export function hasI18nAttrs(element: html.Element): boolean { return element.attrs.some((attr: html.Attribute) => isI18nAttribute(attr.name)); } diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index b75e162fe7..0b477ef57d 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -26,7 +26,7 @@ import {ParseError, ParseSourceSpan} from '../../parse_util'; import {DomElementSchemaRegistry} from '../../schema/dom_element_schema_registry'; import {CssSelector, SelectorMatcher} from '../../selector'; import {BindingParser} from '../../template_parser/binding_parser'; -import {error} from '../../util'; +import {error, partitionArray} from '../../util'; import * as t from '../r3_ast'; import {Identifiers as R3} from '../r3_identifiers'; import {htmlAstToRender3Ast} from '../r3_template_transform'; @@ -36,7 +36,7 @@ import {I18nContext} from './i18n/context'; import {createGoogleGetMsgStatements} from './i18n/get_msg_utils'; import {createLocalizeStatements} from './i18n/localize_utils'; import {I18nMetaVisitor} from './i18n/meta'; -import {assembleBoundTextPlaceholders, assembleI18nBoundString, declareI18nVariable, getTranslationConstPrefix, I18N_ICU_MAPPING_PREFIX, i18nFormatPlaceholderNames, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, placeholdersToParams, TRANSLATION_PREFIX, wrapI18nPlaceholder} from './i18n/util'; +import {assembleBoundTextPlaceholders, assembleI18nBoundString, declareI18nVariable, getTranslationConstPrefix, hasI18nMeta, I18N_ICU_MAPPING_PREFIX, i18nFormatPlaceholderNames, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, placeholdersToParams, TRANSLATION_PREFIX, wrapI18nPlaceholder} from './i18n/util'; import {StylingBuilder, StylingInstruction} from './styling_builder'; import {asLiteral, chainedInstruction, CONTEXT_NAME, getAttrsForDirectiveMatching, getInterpolationArgsLength, IMPLICIT_REFERENCE, invalid, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, trimTrailingNulls, unsupported} from './util'; @@ -848,11 +848,10 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver this.matchDirectives(NG_TEMPLATE_TAG_NAME, template); // prepare attributes parameter (including attributes used for directive matching) - // TODO (FW-1942): exclude i18n attributes from the main attribute list and pass them - // as an `i18nAttrs` argument of the `getAttributeExpressions` function below. + const [i18nStaticAttrs, staticAttrs] = partitionArray(template.attributes, hasI18nMeta); const attrsExprs: o.Expression[] = this.getAttributeExpressions( - template.attributes, template.inputs, template.outputs, undefined, template.templateAttrs, - undefined); + staticAttrs, template.inputs, template.outputs, undefined /* styles */, + template.templateAttrs, i18nStaticAttrs); parameters.push(this.addAttrsToConsts(attrsExprs)); // local refs (ex.: ) @@ -896,12 +895,8 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // Only add normal input/output binding instructions on explicit elements. if (template.tagName === NG_TEMPLATE_TAG_NAME) { - const inputs: t.BoundAttribute[] = []; - const i18nAttrs: (t.TextAttribute|t.BoundAttribute)[] = - template.attributes.filter(attr => !!attr.i18n); - - template.inputs.forEach( - (input: t.BoundAttribute) => (input.i18n ? i18nAttrs : inputs).push(input)); + const [i18nInputs, inputs] = partitionArray(template.inputs, hasI18nMeta); + const i18nAttrs = [...i18nStaticAttrs, ...i18nInputs]; // Add i18n attributes that may act as inputs to directives. If such attributes are present, // generate `i18nAttributes` instruction. Note: we generate it only for explicit diff --git a/packages/compiler/src/util.ts b/packages/compiler/src/util.ts index 1cd8db7061..82b87ef97d 100644 --- a/packages/compiler/src/util.ts +++ b/packages/compiler/src/util.ts @@ -270,4 +270,22 @@ export function newArray(size: number, value?: T): T[] { list.push(value!); } return list; +} + +/** + * Partitions a given array into 2 arrays, based on a boolean value returned by the condition + * function. + * + * @param arr Input array that should be partitioned + * @param conditionFn Condition function that is called for each item in a given array and returns a + * boolean value. + */ +export function partitionArray( + arr: T[], conditionFn: (value: K) => boolean): [T[], T[]] { + const truthy: T[] = []; + const falsy: T[] = []; + arr.forEach(item => { + (conditionFn(item) ? truthy : falsy).push(item); + }); + return [truthy, falsy]; } \ No newline at end of file diff --git a/packages/compiler/test/util_spec.ts b/packages/compiler/test/util_spec.ts index aa8423df2a..dfa0455443 100644 --- a/packages/compiler/test/util_spec.ts +++ b/packages/compiler/test/util_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {escapeRegExp, splitAtColon, stringify, utf8Encode} from '../src/util'; +import {escapeRegExp, partitionArray, splitAtColon, stringify, utf8Encode} from '../src/util'; { describe('util', () => { @@ -83,5 +83,21 @@ import {escapeRegExp, splitAtColon, stringify, utf8Encode} from '../src/util'; expect(stringify(Object.create(null))).toEqual('object'); }); }); + + describe('partitionArray()', () => { + it('should handle empty arrays', () => { + expect(partitionArray([], () => true)).toEqual([[], []]); + }); + + it('should handle arrays with primitive type values', () => { + expect(partitionArray([1, 2, 3], (el: number) => el < 2)).toEqual([[1], [2, 3]]); + }); + + it('should handle arrays of objects', () => { + expect(partitionArray([{id: 1}, {id: 2}, {id: 3}], (el: any) => el.id < 2)).toEqual([ + [{id: 1}], [{id: 2}, {id: 3}] + ]); + }); + }); }); } diff --git a/packages/core/src/render3/node_selector_matcher.ts b/packages/core/src/render3/node_selector_matcher.ts index 5d724774b3..78e7aa78bb 100644 --- a/packages/core/src/render3/node_selector_matcher.ts +++ b/packages/core/src/render3/node_selector_matcher.ts @@ -290,7 +290,11 @@ function matchTemplateAttribute(attrs: TAttributes, name: string): number { if (i > -1) { i++; while (i < attrs.length) { - if (attrs[i] === name) return i; + const attr = attrs[i]; + // Return in case we checked all template attrs and are switching to the next section in the + // attrs array (that starts with a number that represents an attribute marker). + if (typeof attr === 'number') return -1; + if (attr === name) return i; i++; } }