fix(compiler): support directive inputs with interpolations on <ng-template>
s (#35984)
Prior to this commit, Ivy compiler didn't handle directive inputs with interpolations located on `<ng-template>` elements (e.g. `<ng-template dir="{{ field }}">`). That was the case for regular inputs as well as inputs that should be processed via i18n subsystem (e.g. `<ng-template i18n-dir dir="Hello {{ name }}">`). This commit adds support for such expressions for explicit `<ng-template>`s as well as a number of tests to confirm the behavior. Fixes #35752. PR Close #35984
This commit is contained in:
@ -403,6 +403,86 @@ describe('i18n support in the template compiler', () => {
|
||||
verify(input, output);
|
||||
});
|
||||
|
||||
it('should support i18n attributes with interpolations on explicit <ng-template> elements',
|
||||
() => {
|
||||
const input = `
|
||||
<ng-template i18n-title title="Hello {{ name }}"></ng-template>
|
||||
`;
|
||||
|
||||
const output = String.raw `
|
||||
var $I18N_0$;
|
||||
if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) {
|
||||
const $MSG_EXTERNAL_3771704108176831903$$APP_SPEC_TS_1$ = goog.getMsg("Hello {$interpolation}", {
|
||||
"interpolation": "\uFFFD0\uFFFD"
|
||||
});
|
||||
$I18N_0$ = $MSG_EXTERNAL_3771704108176831903$$APP_SPEC_TS_1$;
|
||||
}
|
||||
else {
|
||||
$I18N_0$ = $localize \`Hello $` +
|
||||
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
|
||||
}
|
||||
const $_c2$ = ["title", $I18N_0$];
|
||||
…
|
||||
consts: [[${AttributeMarker.Bindings}, "title"]],
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 0, 0, "ng-template", 0);
|
||||
$r3$.ɵɵi18nAttributes(1, $_c2$);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵi18nExp(ctx.name);
|
||||
$r3$.ɵɵi18nApply(1);
|
||||
}
|
||||
}
|
||||
`;
|
||||
verify(input, output);
|
||||
});
|
||||
|
||||
it('should support i18n attributes with interpolations on explicit <ng-template> elements with structural directives',
|
||||
() => {
|
||||
const input = `
|
||||
<ng-template *ngIf="true" i18n-title title="Hello {{ name }}"></ng-template>
|
||||
`;
|
||||
|
||||
const output = String.raw `
|
||||
var $I18N_0$;
|
||||
if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) {
|
||||
const $MSG_EXTERNAL_3771704108176831903$$APP_SPEC_TS__1$ = goog.getMsg("Hello {$interpolation}", {
|
||||
"interpolation": "\uFFFD0\uFFFD"
|
||||
});
|
||||
$I18N_0$ = $MSG_EXTERNAL_3771704108176831903$$APP_SPEC_TS__1$;
|
||||
}
|
||||
else {
|
||||
$I18N_0$ = $localize \`Hello $` +
|
||||
String.raw `{"\uFFFD0\uFFFD"}:INTERPOLATION:\`;
|
||||
}
|
||||
const $_c2$ = ["title", $I18N_0$];
|
||||
…
|
||||
function MyComponent_0_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵtemplate(0, MyComponent_0_ng_template_0_Template, 0, 0, "ng-template", 1);
|
||||
$r3$.ɵɵi18nAttributes(1, $_c2$);
|
||||
}
|
||||
if (rf & 2) {
|
||||
const $ctx_r2$ = $r3$.ɵɵnextContext();
|
||||
$r3$.ɵɵi18nExp($ctx_r2$.name);
|
||||
$r3$.ɵɵi18nApply(1);
|
||||
}
|
||||
}
|
||||
…
|
||||
consts: [[${AttributeMarker.Template}, "ngIf"], [${AttributeMarker.Bindings}, "title"]],
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵtemplate(0, MyComponent_0_Template, 2, 1, undefined, 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵproperty("ngIf", true);
|
||||
}
|
||||
},
|
||||
`;
|
||||
verify(input, output);
|
||||
});
|
||||
|
||||
it('should not create translations for empty attributes', () => {
|
||||
const input = `
|
||||
<div id="static" i18n-title="m|d" title></div>
|
||||
|
@ -548,6 +548,95 @@ describe('compiler compliance: template', () => {
|
||||
|
||||
});
|
||||
|
||||
it('should allow directive inputs as an interpolated prop on <ng-template>', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Directive, Input} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
class WithInput {
|
||||
@Input() dir: string = '';
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: '<ng-template dir="{{ message }}"></ng-template>',
|
||||
})
|
||||
export class TestComp {
|
||||
message = 'Hello';
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
const result = compile(files, angularFiles);
|
||||
const expectedTemplate = `
|
||||
consts: [[${AttributeMarker.Bindings}, "dir"]],
|
||||
template: function TestComp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$i0$.ɵɵtemplate(0, $TestComp_ng_template_0_Template$, 0, 0, "ng-template", 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$i0$.ɵɵpropertyInterpolate("dir", ctx.message);
|
||||
}
|
||||
},
|
||||
`;
|
||||
expectEmit(result.source, expectedTemplate, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should allow directive inputs as an interpolated prop on <ng-template> (with structural directives)',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Directive, Input} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
class WithInput {
|
||||
@Input() dir: string = '';
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: '<ng-template *ngIf="true" dir="{{ message }}"></ng-template>',
|
||||
})
|
||||
export class TestComp {
|
||||
message = 'Hello';
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
const result = compile(files, angularFiles);
|
||||
|
||||
// Expect that `ɵɵpropertyInterpolate` is generated in the inner template function.
|
||||
const expectedInnerTemplate = `
|
||||
function $TestComp_0_Template$(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$i0$.ɵɵtemplate(0, $TestComp_0_ng_template_0_Template$, 0, 0, "ng-template", 1);
|
||||
}
|
||||
if (rf & 2) {
|
||||
const $ctx_r0$ = i0.ɵɵnextContext();
|
||||
$i0$.ɵɵpropertyInterpolate("dir", $ctx_r0$.message);
|
||||
}
|
||||
}
|
||||
`;
|
||||
expectEmit(result.source, expectedInnerTemplate, 'Incorrect template');
|
||||
|
||||
// Main template should just contain *ngIf property.
|
||||
const expectedMainTemplate = `
|
||||
consts: [[4, "ngIf"], [3, "dir"]],
|
||||
template: function TestComp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$i0$.ɵɵtemplate(0, $TestComp_0_Template$, 1, 1, undefined, 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$i0$.ɵɵproperty("ngIf", true);
|
||||
}
|
||||
},
|
||||
`;
|
||||
expectEmit(result.source, expectedMainTemplate, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should create unique template function names even for similar nested template structures',
|
||||
() => {
|
||||
const files = {
|
||||
|
Reference in New Issue
Block a user