fix(ivy): match attribute selectors for content projection with inline-templates (#29041)

The content projection mechanism is static, in that it only looks at the static
template nodes before directives are matched and change detection is run.
When you have a selector-based content projection the selection is based
on nodes that are available in the template.

For example:

```
<ng-content selector="[some-attr]"></ng-content>
```

would match

```
<div some-attr="..."></div>
```

If you have an inline-template in your projected nodes. For example:

```
<div *ngIf="..." some-attr="..."></div>
```

This gets pre-parsed and converted to a canonical form.

For example:

```
<ng-template [ngIf]="...">
  <div some-attr=".."></div>
</ng-template>
```

Note that only structural attributes (e.g. `*ngIf`) stay with the `<ng-template>`
node. The other attributes move to the contained element inside the template.

When this happens in ivy, the ng-template content is removed
from the component template function and is compiled into its own
template function. But this means that the information about the
attributes that were on the content are lost and the projection
selection mechanism is unable to match the original
`<div *ngIf="..." some-attr>`.

This commit adds support for this in ivy. Attributes are separated into three
groups (Bindings, Templates and "other"). For inline-templates the Bindings
and "other" types are hoisted back from the contained node to the `template()`
instruction, so that they can be used in content projection matching.

PR Close #29041
This commit is contained in:
Pete Bacon Darwin
2019-03-07 08:31:31 +00:00
committed by Kara Erickson
parent e3a401d20c
commit f535f31d78
28 changed files with 357 additions and 194 deletions

View File

@ -788,7 +788,7 @@ describe('compiler compliance', () => {
});`;
const MyComponentDefinition = `
const $c1$ = ["foo", ""];
const $c2$ = ["if", ""];
const $c2$ = [${AttributeMarker.Template}, "if"];
function MyComponent_li_2_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵelementStart(0, "li");
@ -1223,17 +1223,18 @@ describe('compiler compliance', () => {
}
};
const output = `
const $_c0$ = [${AttributeMarker.Bindings}, "ngIf"];
const $_c1$ = ["id", "second"];
const $_c0$ = ["id", "second", ${AttributeMarker.Template}, "ngIf"];
const $_c1$ = ["id", "third", ${AttributeMarker.Template}, "ngIf"];
const $_c2$ = ["id", "second"];
function Cmp_div_0_Template(rf, ctx) { if (rf & 1) {
$r3$.ɵelementStart(0, "div", $_c1$);
$r3$.ɵprojection(1, 1);
$r3$.ɵelementEnd();
$r3$.ɵelementStart(0, "div", $_c2$);
$r3$.ɵprojection(1, 1);
$r3$.ɵelementEnd();
} }
const $_c4$ = ["id", "third"];
const $_c3$ = ["id", "third"];
function Cmp_div_1_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵelementStart(0, "div", $_c4$);
$r3$.ɵelementStart(0, "div", $_c3$);
$r3$.ɵtext(1, " No ng-content, no instructions generated. ");
$r3$.ɵelementEnd();
}
@ -1244,14 +1245,14 @@ describe('compiler compliance', () => {
$r3$.ɵprojection(1);
}
}
const $_c2$ = [[["span", "title", "tofirst"]]];
const $_c3$ = ["span[title=toFirst]"];
const $_c4$ = [[["span", "title", "tofirst"]]];
const $_c5$ = ["span[title=toFirst]"];
template: function Cmp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵprojectionDef($_c2$, $_c3$);
$r3$.ɵprojectionDef($_c4$, $_c5$);
$r3$.ɵtemplate(0, Cmp_div_0_Template, 2, 0, "div", $_c0$);
$r3$.ɵtemplate(1, Cmp_div_1_Template, 2, 0, "div", $_c0$);
$r3$.ɵtemplate(1, Cmp_div_1_Template, 2, 0, "div", $_c1$);
$r3$.ɵtemplate(2, Cmp_ng_template_2_Template, 2, 0, "ng-template");
}
if (rf & 2) {
@ -2112,7 +2113,7 @@ describe('compiler compliance', () => {
const MyComponentDefinition = `
const $c1$ = ["foo", ""];
const $c2$ = ["if", ""];
const $c2$ = [${AttributeMarker.Template}, "if"];
const $c3$ = ["baz", ""];
const $c4$ = ["bar", ""];
function MyComponent_div_3_span_2_Template(rf, ctx) {
@ -2203,9 +2204,9 @@ describe('compiler compliance', () => {
};
const template = `
const $c0$ = ["ngFor", "" , ${AttributeMarker.Bindings}, "ngForOf"];
const $c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"];
const $c1$ = ["foo", ""];
const $c2$ = [${AttributeMarker.Bindings}, "ngIf"];
const $c2$ = [${AttributeMarker.Template}, "ngIf"];
function MyComponent_div_0_span_3_Template(rf, ctx) {
if (rf & 1) {
@ -2430,7 +2431,7 @@ describe('compiler compliance', () => {
`;
const MyComponentDefinition = `
const $t1_attrs$ = ["for", "", ${AttributeMarker.Bindings}, "forOf"];
const $t1_attrs$ = [${AttributeMarker.Template}, "for", "forOf"];
function MyComponent__svg_g_1_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵnamespaceSVG();
@ -2509,7 +2510,7 @@ describe('compiler compliance', () => {
`;
const MyComponentDefinition = `
const $t1_attrs$ = ["for", "", ${AttributeMarker.Bindings}, "forOf"];
const $t1_attrs$ = [${AttributeMarker.Template}, "for", "forOf"];
function MyComponent_li_1_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵelementStart(0, "li");
@ -2591,7 +2592,7 @@ describe('compiler compliance', () => {
};
const MyComponentDefinition = `
const $t4_attrs$ = ["for", "", ${AttributeMarker.Bindings}, "forOf"];
const $t4_attrs$ = [${AttributeMarker.Template}, "for", "forOf"];
function MyComponent_li_1_li_4_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵelementStart(0, "li");

View File

@ -236,7 +236,7 @@ describe('compiler compliance: directives', () => {
const MyComponentDefinition = `
const $_c0$ = [${AttributeMarker.Bindings}, "ngIf"];
const $_c0$ = ["directiveA", "", ${AttributeMarker.Template}, "ngIf"];
const $_c1$ = ["directiveA", ""];
function MyComponent_ng_container_0_Template(rf, ctx) {
if (rf & 1) {
@ -339,7 +339,7 @@ describe('compiler compliance: directives', () => {
// MyComponent definition should be:
const MyComponentDefinition = `
const $c0_a0$ = ["someDirective", ""];
const $c0_a0$ = [${AttributeMarker.Template}, "someDirective"];
MyComponent.ngComponentDef = $r3$.ɵdefineComponent({

View File

@ -417,7 +417,7 @@ describe('i18n support in the view compiler', () => {
`;
const output = String.raw `
const $_c0$ = ["ngFor", "", ${AttributeMarker.Bindings}, "ngForOf"];
const $_c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"];
/**
* @desc d
* @meaning m
@ -548,7 +548,7 @@ describe('i18n support in the view compiler', () => {
`;
const output = String.raw `
const $_c0$ = ["ngFor", "", ${AttributeMarker.Bindings}, "ngForOf"];
const $_c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"];
/**
* @desc d
* @meaning m
@ -1011,7 +1011,7 @@ describe('i18n support in the view compiler', () => {
`;
const output = String.raw `
const $_c0$ = [${AttributeMarker.Bindings}, "ngIf"];
const $_c0$ = [${AttributeMarker.Template}, "ngIf"];
const $MSG_EXTERNAL_7679414751795588050$$APP_SPEC_TS__1$ = goog.getMsg(" Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$closeTagDiv}", {
"interpolation": "\uFFFD0\uFFFD",
"startTagDiv": "\uFFFD#3\uFFFD",
@ -1067,7 +1067,8 @@ describe('i18n support in the view compiler', () => {
const output = String.raw `
const $_c0$ = ["src", "logo.png"];
const $_c1$ = [${AttributeMarker.Bindings}, "ngIf"];
const $_c1$ = ["src", "logo.png", ${AttributeMarker.Template}, "ngIf"];
const $_c2$ = ["src", "logo.png", ${AttributeMarker.Bindings}, "title", ${AttributeMarker.Template}, "ngIf"];
function MyComponent_img_1_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵelement(0, "img", $_c0$);
@ -1076,11 +1077,11 @@ describe('i18n support in the view compiler', () => {
const $MSG_EXTERNAL_2367729185105559721$ = goog.getMsg("App logo #{$interpolation}", {
"interpolation": "\uFFFD0\uFFFD"
});
const $_c2$ = ["title", $MSG_EXTERNAL_2367729185105559721$];
const $_c3$ = ["title", $MSG_EXTERNAL_2367729185105559721$];
function MyComponent_img_2_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵelementStart(0, "img", $_c0$);
$r3$.ɵi18nAttributes(1, $_c2$);
$r3$.ɵi18nAttributes(1, $_c3$);
$r3$.ɵelementEnd();
}
if (rf & 2) {
@ -1096,7 +1097,7 @@ describe('i18n support in the view compiler', () => {
if (rf & 1) {
$r3$.ɵelement(0, "img", $_c0$);
$r3$.ɵtemplate(1, MyComponent_img_1_Template, 1, 0, "img", $_c1$);
$r3$.ɵtemplate(2, MyComponent_img_2_Template, 2, 1, "img", $_c1$);
$r3$.ɵtemplate(2, MyComponent_img_2_Template, 2, 1, "img", $_c2$);
}
if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
@ -1136,7 +1137,7 @@ describe('i18n support in the view compiler', () => {
`;
const output = String.raw `
const $_c0$ = [${AttributeMarker.Bindings}, "ngIf"];
const $_c0$ = [${AttributeMarker.Template}, "ngIf"];
function MyComponent_div_2_div_4_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵi18nStart(0, $I18N_EXTERNAL_1221890473527419724$$APP_SPEC_TS_0$, 2);
@ -1232,7 +1233,7 @@ describe('i18n support in the view compiler', () => {
`;
const output = String.raw `
const $_c0$ = [${AttributeMarker.Bindings}, "ngIf"];
const $_c0$ = [${AttributeMarker.Template}, "ngIf"];
const $MSG_EXTERNAL_119975189388320493$$APP_SPEC_TS__1$ = goog.getMsg("Some other content {$startTagSpan}{$interpolation}{$closeTagSpan}", {
"startTagSpan": "\uFFFD#2\uFFFD",
"interpolation": "\uFFFD0\uFFFD",
@ -1864,15 +1865,16 @@ describe('i18n support in the view compiler', () => {
const $I18N_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$, {
"VAR_SELECT": "\uFFFD0\uFFFD"
});
const $_c0$ = [${AttributeMarker.Bindings}, "ngIf"];
const $_c1$ = ["title", "icu only"];
const $_c0$ = ["title", "icu only", ${AttributeMarker.Template}, "ngIf"];
const $_c1$ = ["title", "icu and text", ${AttributeMarker.Template}, "ngIf"];
const $_c2$ = ["title", "icu only"];
const $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}");
const $I18N_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$, {
"VAR_SELECT": "\uFFFD0\uFFFD"
});
function MyComponent_div_2_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵelementStart(0, "div", $_c1$);
$r3$.ɵelementStart(0, "div", $_c2$);
$r3$.ɵi18n(1, $I18N_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$);
$r3$.ɵelementEnd();
}
@ -1883,7 +1885,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵi18nApply(1);
}
}
const $_c2$ = ["title", "icu and text"];
const $_c3$ = ["title", "icu and text"];
const $MSG_EXTERNAL_1922743304863699161$$APP_SPEC_TS__5$ = goog.getMsg("{VAR_SELECT, select, 0 {no emails} 1 {one email} other {{$interpolation} emails}}", {
"interpolation": "\uFFFD1\uFFFD"
});
@ -1892,7 +1894,7 @@ describe('i18n support in the view compiler', () => {
});
function MyComponent_div_3_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵelementStart(0, "div", $_c2$);
$r3$.ɵelementStart(0, "div", $_c3$);
$r3$.ɵtext(1, " You have ");
$r3$.ɵi18n(2, $I18N_EXTERNAL_1922743304863699161$$APP_SPEC_TS__5$);
$r3$.ɵtext(3, ". ");
@ -1915,7 +1917,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵi18n(1, $I18N_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$);
$r3$.ɵelementEnd();
$r3$.ɵtemplate(2, MyComponent_div_2_Template, 2, 1, "div", $_c0$);
$r3$.ɵtemplate(3, MyComponent_div_3_Template, 4, 2, "div", $_c0$);
$r3$.ɵtemplate(3, MyComponent_div_3_Template, 4, 2, "div", $_c1$);
}
if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
@ -2113,7 +2115,7 @@ describe('i18n support in the view compiler', () => {
const $I18N_APP_SPEC_TS_2$ = $r3$.ɵi18nPostprocess($MSG_APP_SPEC_TS_2$, {
"VAR_SELECT": "\uFFFD1\uFFFD"
});
const $_c3$ = [${AttributeMarker.Bindings}, "ngIf"];
const $_c3$ = [${AttributeMarker.Template}, "ngIf"];
const $MSG_APP_SPEC_TS__4$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}");
const $I18N_APP_SPEC_TS__4$ = $r3$.ɵi18nPostprocess($MSG_APP_SPEC_TS__4$, {
"VAR_SELECT": "\uFFFD0:1\uFFFD"
@ -2223,7 +2225,7 @@ describe('i18n support in the view compiler', () => {
const $I18N_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$, {
"VAR_SELECT": "\uFFFD0\uFFFD"
});
const $_c0$ = [${AttributeMarker.Bindings}, "ngIf"];
const $_c0$ = [${AttributeMarker.Template}, "ngIf"];
const $MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}");
const $I18N_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$, {
"VAR_SELECT": "\uFFFD0:1\uFFFD"
@ -2287,7 +2289,7 @@ describe('i18n support in the view compiler', () => {
const $I18N_EXTERNAL_7825031864601787094$$APP_SPEC_TS_1$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7825031864601787094$$APP_SPEC_TS_1$, {
"VAR_SELECT": "\uFFFD0\uFFFD"
});
const $_c0$ = [${AttributeMarker.Bindings}, "ngIf"];
const $_c0$ = [${AttributeMarker.Template}, "ngIf"];
const $MSG_EXTERNAL_2310343208266678305$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {$interpolation}}}", {
"interpolation": "\uFFFD1:1\uFFFD"
});

View File

@ -133,7 +133,7 @@ describe('compiler compliance: listen()', () => {
};
const template = `
const $t0_attrs$ = [${AttributeMarker.Bindings}, "ngIf"];
const $t0_attrs$ = [${AttributeMarker.Template}, "ngIf"];
const $e_attrs$ = [${AttributeMarker.Bindings}, "click"];
function MyComponent_div_0_Template(rf, ctx) {

View File

@ -50,13 +50,14 @@ describe('compiler compliance: template', () => {
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
const $c0$ = ["ngFor", "", ${AttributeMarker.Bindings}, "ngForOf"];
const $e0_attrs$ = [${AttributeMarker.Bindings}, "title", "click"];
const $c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"];
const $c1$ = [${AttributeMarker.Bindings}, "title", "click", ${AttributeMarker.Template}, "ngFor", "ngForOf"];
const $c2$ = [${AttributeMarker.Bindings}, "title", "click"];
function MyComponent_ul_0_li_1_div_1_Template(rf, ctx) {
if (rf & 1) {
const $s$ = $i0$.ɵgetCurrentView();
$i0$.ɵelementStart(0, "div", $e0_attrs$);
$i0$.ɵelementStart(0, "div", $c2$);
$i0$.ɵlistener("click", function MyComponent_ul_0_li_1_div_1_Template_div_click_0_listener($event){
$i0$.ɵrestoreView($s$);
const $inner$ = ctx.$implicit;
@ -83,7 +84,7 @@ describe('compiler compliance: template', () => {
function MyComponent_ul_0_li_1_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵelementStart(0, "li");
$i0$.ɵtemplate(1, MyComponent_ul_0_li_1_div_1_Template, 2, 2, "div", _c0);
$i0$.ɵtemplate(1, MyComponent_ul_0_li_1_div_1_Template, 2, 2, "div", $c1$);
$i0$.ɵelementEnd();
}
if (rf & 2) {
@ -96,7 +97,7 @@ describe('compiler compliance: template', () => {
function MyComponent_ul_0_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵelementStart(0, "ul");
$i0$.ɵtemplate(1, MyComponent_ul_0_li_1_Template, 2, 1, "li", _c0);
$i0$.ɵtemplate(1, MyComponent_ul_0_li_1_Template, 2, 1, "li", $c0$);
$i0$.ɵelementEnd();
}
if (rf & 2) {
@ -108,7 +109,7 @@ describe('compiler compliance: template', () => {
// ...
template:function MyComponent_Template(rf, ctx){
if (rf & 1) {
$i0$.ɵtemplate(0, MyComponent_ul_0_Template, 2, 1, "ul", _c0);
$i0$.ɵtemplate(0, MyComponent_ul_0_Template, 2, 1, "ul", $c0$);
}
if (rf & 2) {
$i0$.ɵelementProperty(0, "ngForOf", $i0$.ɵbind(ctx.items));
@ -144,7 +145,7 @@ describe('compiler compliance: template', () => {
};
const template = `
const $t0_attrs$ = ["ngFor", "", ${AttributeMarker.Bindings}, "ngForOf"];
const $t0_attrs$ = [${AttributeMarker.Bindings}, "click", ${AttributeMarker.Template}, "ngFor", "ngForOf"];
const $e_attrs$ = [${AttributeMarker.Bindings}, "click"];
function MyComponent_div_0_Template(rf, ctx) {
@ -199,7 +200,7 @@ describe('compiler compliance: template', () => {
};
const template = `
const $c0$ = ["ngFor", "", ${AttributeMarker.Bindings}, "ngForOf"];
const $c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"];
function MyComponent_span_0_Template(rf, ctx) {
if (rf & 1) {
@ -253,8 +254,8 @@ describe('compiler compliance: template', () => {
};
const template = `
const $c0$ = ["ngFor", "", ${AttributeMarker.Bindings}, "ngForOf"];
const $c1$ = [${AttributeMarker.Bindings}, "ngIf"];
const $c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"];
const $c1$ = [${AttributeMarker.Template}, "ngIf"];
function MyComponent_div_0_span_1_Template(rf, ctx) {
if (rf & 1) {
@ -326,7 +327,7 @@ describe('compiler compliance: template', () => {
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
const $c0$ = ["ngFor", "", ${AttributeMarker.Bindings}, "ngForOf"];
const $c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"];
function MyComponent_div_0_div_1_div_1_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵelementStart(0, "div");
@ -344,7 +345,7 @@ describe('compiler compliance: template', () => {
function MyComponent_div_0_div_1_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵelementStart(0, "div");
$i0$.ɵtemplate(1, MyComponent_div_0_div_1_div_1_Template, 2, 2, "div", _c0);
$i0$.ɵtemplate(1, MyComponent_div_0_div_1_div_1_Template, 2, 2, "div", $c0$);
$i0$.ɵelementEnd();
}
if (rf & 2) {
@ -357,7 +358,7 @@ describe('compiler compliance: template', () => {
function MyComponent_div_0_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵelementStart(0, "div");
$i0$.ɵtemplate(1, MyComponent_div_0_div_1_Template, 2, 1, "div", _c0);
$i0$.ɵtemplate(1, MyComponent_div_0_div_1_Template, 2, 1, "div", $c0$);
$i0$.ɵelementEnd();
}
if (rf & 2) {
@ -369,7 +370,7 @@ describe('compiler compliance: template', () => {
// ...
template:function MyComponent_Template(rf, ctx){
if (rf & 1) {
$i0$.ɵtemplate(0, MyComponent_div_0_Template, 2, 1, "div", _c0);
$i0$.ɵtemplate(0, MyComponent_div_0_Template, 2, 1, "div", $c0$);
}
if (rf & 2) {
$i0$.ɵelementProperty(0, "ngForOf", $i0$.ɵbind(ctx.items));
@ -645,7 +646,7 @@ describe('compiler compliance: template', () => {
};
const template = `
const $c0$ = [${AttributeMarker.Bindings}, "ngIf"];
const $c0$ = [${AttributeMarker.Template}, "ngIf"];
function MyComponent_div_0_Template(rf, ctx) {
if (rf & 1) {