fix(ivy): correctly set `TView.firstChild` with runtime i18n (#30920)

`TView.firstChild` was defined as the first node with index 0, but when we use runtime i18n then the instruction `i18nStart` can be the one with index 0 and the first node will be index 1 or more.
With this fix we set `TView.firstChild` when we see the first node with index 0 or later if `TView.firstChild` is still null.

FW-1367 #resolve
PR Close #30920
This commit is contained in:
Olivier Combe 2019-06-07 18:20:35 +02:00 committed by Igor Minar
parent 2b9a4cc6a6
commit 4155ed439a
3 changed files with 39 additions and 4 deletions

View File

@ -488,10 +488,10 @@ function i18nStartFirstPass(
tView.data[index + HEADER_OFFSET] = tI18n; tView.data[index + HEADER_OFFSET] = tI18n;
} }
function appendI18nNode(tNode: TNode, parentTNode: TNode, previousTNode: TNode | null): TNode { function appendI18nNode(
tNode: TNode, parentTNode: TNode, previousTNode: TNode | null, viewData: LView): TNode {
ngDevMode && ngDevMode.rendererMoveNode++; ngDevMode && ngDevMode.rendererMoveNode++;
const nextNode = tNode.next; const nextNode = tNode.next;
const viewData = getLView();
if (!previousTNode) { if (!previousTNode) {
previousTNode = parentTNode; previousTNode = parentTNode;
} }
@ -737,7 +737,7 @@ function readCreateOpCodes(
assertDefined( assertDefined(
currentTNode !, currentTNode !,
`You need to create or select a node before you can insert it into the DOM`); `You need to create or select a node before you can insert it into the DOM`);
previousTNode = appendI18nNode(currentTNode !, destinationTNode, previousTNode); previousTNode = appendI18nNode(currentTNode !, destinationTNode, previousTNode, viewData);
break; break;
case I18nMutateOpCode.Select: case I18nMutateOpCode.Select:
const nodeIndex = opCode >>> I18nMutateOpCode.SHIFT_REF; const nodeIndex = opCode >>> I18nMutateOpCode.SHIFT_REF;

View File

@ -277,7 +277,9 @@ function createTNodeAtIndex(
const tParentNode = parentInSameView ? parent as TElementNode | TContainerNode : null; const tParentNode = parentInSameView ? parent as TElementNode | TContainerNode : null;
const tNode = tView.data[adjustedIndex] = const tNode = tView.data[adjustedIndex] =
createTNode(tParentNode, type, adjustedIndex, name, attrs); createTNode(tParentNode, type, adjustedIndex, name, attrs);
if (index === 0) { // The first node is not always the one at index 0, in case of i18n, index 0 can be the
// instruction `i18nStart` and the first node has the index 1 or more
if (index === 0 || !tView.firstChild) {
tView.firstChild = tNode; tView.firstChild = tNode;
} }
// Now link ourselves into the tree. // Now link ourselves into the tree.

View File

@ -11,6 +11,7 @@ import localeRo from '@angular/common/locales/ro';
import {Component, ContentChild, ContentChildren, Directive, HostBinding, Input, LOCALE_ID, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef, ɵi18nConfigureLocalize} from '@angular/core'; import {Component, ContentChild, ContentChildren, Directive, HostBinding, Input, LOCALE_ID, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef, ɵi18nConfigureLocalize} from '@angular/core';
import {setDelayProjection} from '@angular/core/src/render3/instructions/projection'; import {setDelayProjection} from '@angular/core/src/render3/instructions/projection';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing'; import {onlyInIvy} from '@angular/private/testing';
@ -372,6 +373,38 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
expect(child).toHaveText('Mon logo'); expect(child).toHaveText('Mon logo');
} }
}); });
it('should correctly find context for an element inside i18n section in <ng-template>', () => {
@Directive({selector: '[myDir]'})
class Dir {
condition = true;
}
@Component({
selector: 'my-cmp',
template: `
<div *ngIf="isLogged; else notLoggedIn">
<span>Logged in</span>
</div>
<ng-template #notLoggedIn i18n>
<a myDir>Not logged in</a>
</ng-template>
`,
})
class Cmp {
isLogged = false;
}
TestBed.configureTestingModule({
declarations: [Cmp, Dir],
});
const fixture = TestBed.createComponent(Cmp);
fixture.detectChanges();
const a = fixture.debugElement.query(By.css('a'));
const dir = a.injector.get(Dir);
expect(dir.condition).toEqual(true);
});
}); });
describe('should support ICU expressions', () => { describe('should support ICU expressions', () => {