Skip to content

Commit 23e0d65

Browse files
crisbetojasonaden
authored andcommitted
perf(ivy): add self-closing elementContainer instruction (angular#31444)
Adds a new `elementContainer` instruction that can be used to avoid two instruction (`elementContainerStart` and `elementContainerEnd`) for `ng-container` that has text-only content. This is particularly useful when we have `ng-container` inside i18n sections. This PR resolves FW-1105. PR Close angular#31444
1 parent e92fb68 commit 23e0d65

File tree

10 files changed

+135
-18
lines changed

10 files changed

+135
-18
lines changed

packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ describe('compiler compliance', () => {
253253
expectEmit(result.source, template, 'Incorrect template');
254254
});
255255

256-
it('should generate elementContainerStart/End instructions for empty <ng-container>', () => {
256+
it('should generate self-closing elementContainer instruction for empty <ng-container>', () => {
257257
const files = {
258258
app: {
259259
'spec.ts': `
@@ -276,8 +276,7 @@ describe('compiler compliance', () => {
276276
277277
template: function MyComponent_Template(rf, ctx) {
278278
if (rf & 1) {
279-
i0.ɵɵelementContainerStart(0);
280-
i0.ɵɵelementContainerEnd();
279+
i0.ɵɵelementContainer(0);
281280
}
282281
}
283282
`;

packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,9 +1977,8 @@ describe('i18n support in the view compiler', () => {
19771977
$r3$.ɵɵelementStart(0, "div");
19781978
$r3$.ɵɵi18nStart(1, $I18N_0$);
19791979
$r3$.ɵɵtemplate(2, MyComponent_ng_template_2_Template, 2, 3, "ng-template");
1980-
$r3$.ɵɵelementContainerStart(3);
1980+
$r3$.ɵɵelementContainer(3);
19811981
$r3$.ɵɵpipe(4, "uppercase");
1982-
$r3$.ɵɵelementContainerEnd();
19831982
$r3$.ɵɵi18nEnd();
19841983
$r3$.ɵɵelementEnd();
19851984
}
@@ -2324,6 +2323,76 @@ describe('i18n support in the view compiler', () => {
23242323

23252324
verify(input, output);
23262325
});
2326+
2327+
it('should generate a self-closing container instruction for ng-container inside i18n', () => {
2328+
const input = `
2329+
<div i18n>
2330+
Hello <ng-container>there</ng-container>
2331+
</div>
2332+
`;
2333+
2334+
const output = String.raw `
2335+
var $I18N_0$;
2336+
if (ngI18nClosureMode) {
2337+
const $MSG_APP_SPEC_TS_1$ = goog.getMsg(" Hello {$startTagNgContainer}there{$closeTagNgContainer}", { "startTagNgContainer": "\uFFFD#2\uFFFD", "closeTagNgContainer": "\uFFFD/#2\uFFFD" });
2338+
$I18N_0$ = $MSG_APP_SPEC_TS_1$;
2339+
}
2340+
else {
2341+
$I18N_0$ = $r3$.ɵɵi18nLocalize(" Hello {$startTagNgContainer}there{$closeTagNgContainer}", { "startTagNgContainer": "\uFFFD#2\uFFFD", "closeTagNgContainer": "\uFFFD/#2\uFFFD" });
2342+
}
2343+
2344+
consts: 3,
2345+
vars: 0,
2346+
template: function MyComponent_Template(rf, ctx) {
2347+
if (rf & 1) {
2348+
$r3$.ɵɵelementStart(0, "div");
2349+
$r3$.ɵɵi18nStart(1, I18N_0);
2350+
$r3$.ɵɵelementContainer(2);
2351+
$r3$.ɵɵi18nEnd();
2352+
$r3$.ɵɵelementEnd();
2353+
}
2354+
}
2355+
`;
2356+
2357+
verify(input, output);
2358+
});
2359+
2360+
it('should not generate a self-closing container instruction for ng-container with non-text content inside i18n',
2361+
() => {
2362+
const input = `
2363+
<div i18n>
2364+
Hello <ng-container>there <strong>!</strong></ng-container>
2365+
</div>
2366+
`;
2367+
2368+
const output = String.raw `
2369+
var $I18N_0$;
2370+
if (ngI18nClosureMode) {
2371+
const $MSG_APP_SPEC_TS_1$ = goog.getMsg(" Hello {$startTagNgContainer}there {$startTagStrong}!{$closeTagStrong}{$closeTagNgContainer}", { "startTagNgContainer": "\uFFFD#2\uFFFD", "startTagStrong": "\uFFFD#3\uFFFD", "closeTagStrong": "\uFFFD/#3\uFFFD", "closeTagNgContainer": "\uFFFD/#2\uFFFD" });
2372+
$I18N_0$ = $MSG_APP_SPEC_TS_1$;
2373+
}
2374+
else {
2375+
$I18N_0$ = $r3$.ɵɵi18nLocalize(" Hello {$startTagNgContainer}there {$startTagStrong}!{$closeTagStrong}{$closeTagNgContainer}", { "startTagNgContainer": "\uFFFD#2\uFFFD", "startTagStrong": "\uFFFD#3\uFFFD", "closeTagStrong": "\uFFFD/#3\uFFFD", "closeTagNgContainer": "\uFFFD/#2\uFFFD" });
2376+
}
2377+
2378+
consts: 4,
2379+
vars: 0,
2380+
template: function MyComponent_Template(rf, ctx) {
2381+
if (rf & 1) {
2382+
$r3$.ɵɵelementStart(0, "div");
2383+
$r3$.ɵɵi18nStart(1, I18N_0);
2384+
$r3$.ɵɵelementContainerStart(2);
2385+
$r3$.ɵɵelement(3, "strong");
2386+
$r3$.ɵɵelementContainerEnd();
2387+
$r3$.ɵɵi18nEnd();
2388+
$r3$.ɵɵelementEnd();
2389+
}
2390+
}
2391+
`;
2392+
2393+
verify(input, output);
2394+
});
2395+
23272396
});
23282397

23292398
describe('whitespace preserving mode', () => {

packages/compiler/src/render3/r3_identifiers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ export class Identifiers {
6666
static elementContainerEnd:
6767
o.ExternalReference = {name: 'ɵɵelementContainerEnd', moduleName: CORE};
6868

69+
static elementContainer: o.ExternalReference = {name: 'ɵɵelementContainer', moduleName: CORE};
70+
6971
static styling: o.ExternalReference = {name: 'ɵɵstyling', moduleName: CORE};
7072

7173
static styleMap: o.ExternalReference = {name: 'ɵɵstyleMap', moduleName: CORE};

packages/compiler/src/render3/view/template.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -613,23 +613,21 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
613613
this.i18n.appendElement(element.i18n !, elementIndex);
614614
}
615615

616-
const hasChildren = () => {
617-
if (!isI18nRootElement && this.i18n) {
618-
// we do not append text node instructions and ICUs inside i18n section,
619-
// so we exclude them while calculating whether current element has children
620-
return !hasTextChildrenOnly(element.children);
621-
}
622-
return element.children.length > 0;
623-
};
616+
// Note that we do not append text node instructions and ICUs inside i18n section,
617+
// so we exclude them while calculating whether current element has children
618+
const hasChildren = (!isI18nRootElement && this.i18n) ? !hasTextChildrenOnly(element.children) :
619+
element.children.length > 0;
624620

625-
const createSelfClosingInstruction = !stylingBuilder.hasBindings && !isNgContainer &&
626-
element.outputs.length === 0 && i18nAttrs.length === 0 && !hasChildren();
621+
const createSelfClosingInstruction = !stylingBuilder.hasBindings &&
622+
element.outputs.length === 0 && i18nAttrs.length === 0 && !hasChildren;
627623

628624
const createSelfClosingI18nInstruction = !createSelfClosingInstruction &&
629625
!stylingBuilder.hasBindings && hasTextChildrenOnly(element.children);
630626

631627
if (createSelfClosingInstruction) {
632-
this.creationInstruction(element.sourceSpan, R3.element, trimTrailingNulls(parameters));
628+
this.creationInstruction(
629+
element.sourceSpan, isNgContainer ? R3.elementContainer : R3.element,
630+
trimTrailingNulls(parameters));
633631
} else {
634632
this.creationInstruction(
635633
element.sourceSpan, isNgContainer ? R3.elementContainerStart : R3.elementStart,

packages/core/src/core_render3_private_export.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export {
118118
ɵɵallocHostVars,
119119
ɵɵelementContainerStart,
120120
ɵɵelementContainerEnd,
121+
ɵɵelementContainer,
121122
ɵɵstyling,
122123
ɵɵstyleMap,
123124
ɵɵclassMap,

packages/core/src/render3/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export {
5656
ɵɵdirectiveInject,
5757

5858
ɵɵelement,
59+
ɵɵelementContainer,
5960
ɵɵelementContainerEnd,
6061

6162
ɵɵelementContainerStart,

packages/core/src/render3/instructions/element_container.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,19 @@ export function ɵɵelementContainerEnd(): void {
100100

101101
registerPostOrderHooks(tView, previousOrParentTNode);
102102
}
103+
104+
/**
105+
* Creates an empty logical container using {@link elementContainerStart}
106+
* and {@link elementContainerEnd}
107+
*
108+
* @param index Index of the element in the LView array
109+
* @param attrs Set of attributes to be used when matching directives.
110+
* @param localRefs A set of local reference bindings on the element.
111+
*
112+
* @codeGenApi
113+
*/
114+
export function ɵɵelementContainer(
115+
index: number, attrs?: TAttributes | null, localRefs?: string[] | null): void {
116+
ɵɵelementContainerStart(index, attrs, localRefs);
117+
ɵɵelementContainerEnd();
118+
}

packages/core/src/render3/jit/environment.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export const angularCoreEnv: {[name: string]: Function} =
6161
'ɵɵelement': r3.ɵɵelement,
6262
'ɵɵelementContainerStart': r3.ɵɵelementContainerStart,
6363
'ɵɵelementContainerEnd': r3.ɵɵelementContainerEnd,
64+
'ɵɵelementContainer': r3.ɵɵelementContainer,
6465
'ɵɵpureFunction0': r3.ɵɵpureFunction0,
6566
'ɵɵpureFunction1': r3.ɵɵpureFunction1,
6667
'ɵɵpureFunction2': r3.ɵɵpureFunction2,

packages/core/test/acceptance/i18n_spec.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {registerLocaleData} from '@angular/common';
1010
import localeRo from '@angular/common/locales/ro';
11-
import {Component, ContentChild, ContentChildren, Directive, HostBinding, Input, LOCALE_ID, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef, ɵi18nConfigureLocalize} from '@angular/core';
11+
import {Component, ContentChild, ContentChildren, Directive, HostBinding, Input, LOCALE_ID, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef, ɵi18nConfigureLocalize, Pipe, PipeTransform} from '@angular/core';
1212
import {setDelayProjection} from '@angular/core/src/render3/instructions/projection';
1313
import {TestBed} from '@angular/core/testing';
1414
import {By} from '@angular/platform-browser';
@@ -18,7 +18,7 @@ import {onlyInIvy} from '@angular/private/testing';
1818

1919
onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
2020
beforeEach(() => {
21-
TestBed.configureTestingModule({declarations: [AppComp, DirectiveWithTplRef]});
21+
TestBed.configureTestingModule({declarations: [AppComp, DirectiveWithTplRef, UppercasePipe]});
2222
});
2323

2424
afterEach(() => { setDelayProjection(false); });
@@ -315,6 +315,29 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
315315
}
316316
});
317317

318+
it('should be able to act as child elements inside i18n block (text + pipes)', () => {
319+
// Note: for some reason keeping this key inline causes clang to reformat the entire file
320+
// in a very weird way. Keeping it separated like this seems to make it happy.
321+
const key = '{$startTagNgTemplate}Hello {$interpolation}{$closeTagNgTemplate}' +
322+
'{$startTagNgContainer}Bye {$interpolation}{$closeTagNgContainer}';
323+
324+
ɵi18nConfigureLocalize({
325+
translations: {
326+
[key]:
327+
'{$startTagNgTemplate}Hej {$interpolation}{$closeTagNgTemplate}{$startTagNgContainer}Vi ses {$interpolation}{$closeTagNgContainer}'
328+
}
329+
});
330+
const fixture = initWithTemplate(AppComp, `
331+
<div i18n>
332+
<ng-template tplRef>Hello {{name | uppercase}}</ng-template>
333+
<ng-container>Bye {{name | uppercase}}</ng-container>
334+
</div>
335+
`);
336+
337+
const element = fixture.nativeElement.firstChild;
338+
expect(element.textContent.replace(/\s+/g, ' ').trim()).toBe('Hej ANGULARVi ses ANGULAR');
339+
});
340+
318341
it('should be able to handle deep nested levels with templates', () => {
319342
ɵi18nConfigureLocalize({
320343
translations: {
@@ -1463,3 +1486,8 @@ class DirectiveWithTplRef {
14631486
constructor(public vcRef: ViewContainerRef, public tplRef: TemplateRef<{}>) {}
14641487
ngOnInit() { this.vcRef.createEmbeddedView(this.tplRef, {}); }
14651488
}
1489+
1490+
@Pipe({name: 'uppercase'})
1491+
class UppercasePipe implements PipeTransform {
1492+
transform(value: string) { return value.toUpperCase(); }
1493+
}

tools/public_api_guard/core/core.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,8 @@ export declare function ɵɵdisableBindings(): void;
853853

854854
export declare function ɵɵelement(index: number, name: string, attrs?: TAttributes | null, localRefs?: string[] | null): void;
855855

856+
export declare function ɵɵelementContainer(index: number, attrs?: TAttributes | null, localRefs?: string[] | null): void;
857+
856858
export declare function ɵɵelementContainerEnd(): void;
857859

858860
export declare function ɵɵelementContainerStart(index: number, attrs?: TAttributes | null, localRefs?: string[] | null): void;

0 commit comments

Comments
 (0)