From bd40c89688ea28f9168ba709ff823665dfe3871d Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Fri, 25 Oct 2019 16:54:27 -0700 Subject: [PATCH] fix(ivy): handle elements with local refs in i18n blocks (#33415) Prior to this commit, i18n logic which ensures that elements removed in a translation are also removed in DOM, didn't take into account the fact that elements may have local refs. As a result, remove operation failed, since there is no corresponding tNode found. This commit updates the logic to skip all local refs while going though the list of nodes to ensure that DOM matches elements present in translation. PR Close #33415 --- packages/core/src/render3/i18n.ts | 17 ++++++++-- packages/core/test/acceptance/i18n_spec.ts | 37 ++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 3df1f9b71d5e8..09c8a5f745a8f 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -683,10 +683,21 @@ function i18nEndFirstPass(lView: LView, tView: TView) { const visitedNodes = readCreateOpCodes(rootIndex, tI18n.create, lView); // Remove deleted nodes - for (let i = rootIndex + 1; i <= lastCreatedNode.index - HEADER_OFFSET; i++) { - if (visitedNodes.indexOf(i) === -1) { - removeNode(i, lView, /* markAsDetached */ true); + let index = rootIndex + 1; + while (index <= lastCreatedNode.index - HEADER_OFFSET) { + if (visitedNodes.indexOf(index) === -1) { + removeNode(index, lView, /* markAsDetached */ true); } + // Check if an element has any local refs and skip them + const tNode = getTNode(index, lView); + if (tNode && (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) && + tNode.localNames !== null) { + // Divide by 2 to get the number of local refs, + // since they are stored as an array that also includes directive indexes, + // i.e. ["localRef", directiveIndex, ...] + index += tNode.localNames.length >> 1; + } + index++; } } diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index 16fd89ffd5f29..99b184896a2f9 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -287,6 +287,43 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { expect(instance.clicks).toBe(1); }); + it('should support local refs inside i18n block', () => { + loadTranslations({ + [computeMsgId( + '{$START_TAG_NG_CONTAINER} One {$CLOSE_TAG_NG_CONTAINER}' + + '{$START_TAG_DIV} Two {$CLOSE_TAG_DIV}' + + '{$START_TAG_SPAN} Three {$CLOSE_TAG_SPAN}')]: + '{$START_TAG_NG_CONTAINER} Une {$CLOSE_TAG_NG_CONTAINER}' + + '{$START_TAG_DIV} Deux {$CLOSE_TAG_DIV}' + + '{$START_TAG_SPAN} Trois {$CLOSE_TAG_SPAN}' + }); + const fixture = initWithTemplate(AppComp, ` +
+ One +
Two
+ Three +
+ `); + expect(fixture.nativeElement.textContent).toBe(' Une Deux Trois '); + }); + + it('should handle local refs correctly in case an element is removed in translation', () => { + loadTranslations({ + [computeMsgId( + '{$START_TAG_NG_CONTAINER} One {$CLOSE_TAG_NG_CONTAINER}' + + '{$START_TAG_DIV} Two {$CLOSE_TAG_DIV}' + + '{$START_TAG_SPAN} Three {$CLOSE_TAG_SPAN}')]: '{$START_TAG_DIV} Deux {$CLOSE_TAG_DIV}' + }); + const fixture = initWithTemplate(AppComp, ` +
+ One +
Two
+ Three +
+ `); + expect(fixture.nativeElement.textContent).toBe(' Deux '); + }); + describe('ng-container and ng-template support', () => { it('should support ng-container', () => { loadTranslations({[computeMsgId('text')]: 'texte'});