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$.ɵ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) {

View File

@ -81,10 +81,10 @@ export class Element implements Node {
export class Template implements Node {
constructor(
public tagName: string, public attributes: TextAttribute[], public inputs: BoundAttribute[],
public outputs: BoundEvent[], public children: Node[], public references: Reference[],
public variables: Variable[], public sourceSpan: ParseSourceSpan,
public startSourceSpan: ParseSourceSpan|null, public endSourceSpan: ParseSourceSpan|null,
public i18n?: I18nAST) {}
public outputs: BoundEvent[], public templateAttrs: (BoundAttribute|TextAttribute)[],
public children: Node[], public references: Reference[], public variables: Variable[],
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan|null,
public endSourceSpan: ParseSourceSpan|null, public i18n?: I18nAST) {}
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitTemplate(this); }
}
@ -189,15 +189,18 @@ export class TransformVisitor implements Visitor<Node> {
const newAttributes = transformAll(this, template.attributes);
const newInputs = transformAll(this, template.inputs);
const newOutputs = transformAll(this, template.outputs);
const newTemplateAttrs = transformAll(this, template.templateAttrs);
const newChildren = transformAll(this, template.children);
const newReferences = transformAll(this, template.references);
const newVariables = transformAll(this, template.variables);
if (newAttributes != template.attributes || newInputs != template.inputs ||
newOutputs != template.outputs || newChildren != template.children ||
newReferences != template.references || newVariables != template.variables) {
newOutputs != template.outputs || newTemplateAttrs != template.templateAttrs ||
newChildren != template.children || newReferences != template.references ||
newVariables != template.variables) {
return new Template(
template.tagName, newAttributes, newInputs, newOutputs, newChildren, newReferences,
newVariables, template.sourceSpan, template.startSourceSpan, template.endSourceSpan);
template.tagName, newAttributes, newInputs, newOutputs, newTemplateAttrs, newChildren,
newReferences, newVariables, template.sourceSpan, template.startSourceSpan,
template.endSourceSpan);
}
return template;
}

View File

@ -177,8 +177,9 @@ class HtmlAstToIvyAst implements html.Visitor {
const attrs = this.extractAttributes(element.name, parsedProperties, i18nAttrsMeta);
parsedElement = new t.Template(
element.name, attributes, attrs.bound, boundEvents, children, references, variables,
element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
element.name, attributes, attrs.bound, boundEvents, [/* no template attributes */],
children, references, variables, element.sourceSpan, element.startSourceSpan,
element.endSourceSpan, element.i18n);
} else {
const attrs = this.extractAttributes(element.name, parsedProperties, i18nAttrsMeta);
parsedElement = new t.Element(
@ -187,10 +188,25 @@ class HtmlAstToIvyAst implements html.Visitor {
}
if (elementHasInlineTemplate) {
// If this node is an inline-template (e.g. has *ngFor) then we need to create a template
// node that contains this node.
// Moreover, if the node is an element, then we need to hoist its attributes to the template
// node for matching against content projection selectors.
const attrs = this.extractAttributes('ng-template', templateParsedProperties, i18nAttrsMeta);
const templateAttrs: (t.TextAttribute | t.BoundAttribute)[] = [];
attrs.literal.forEach(attr => templateAttrs.push(attr));
attrs.bound.forEach(attr => templateAttrs.push(attr));
const hoistedAttrs = parsedElement instanceof t.Element ?
{
attributes: parsedElement.attributes,
inputs: parsedElement.inputs,
outputs: parsedElement.outputs,
} :
{attributes: [], inputs: [], outputs: []};
// TODO(pk): test for this case
parsedElement = new t.Template(
(parsedElement as t.Element).name, attrs.literal, attrs.bound, [], [parsedElement], [],
(parsedElement as t.Element).name, hoistedAttrs.attributes, hoistedAttrs.inputs,
hoistedAttrs.outputs, templateAttrs, [parsedElement], [/* no references */],
templateVariables, element.sourceSpan, element.startSourceSpan, element.endSourceSpan,
element.i18n);
}

View File

@ -576,9 +576,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
attributes.push(...getAttributeNameLiterals(attr.name), o.literal(attr.value));
});
// this will build the instructions so that they fall into the following syntax
// add attributes for directive matching purposes
attributes.push(...this.prepareBindingsAttrs(allOtherInputs, element.outputs, stylingBuilder));
// add attributes for directive and projection matching purposes
attributes.push(...this.prepareNonRenderAttrs(allOtherInputs, element.outputs, stylingBuilder));
parameters.push(this.toAttrsParam(attributes));
// local refs (ex.: <div #foo #bar="baz">)
@ -774,6 +773,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
}
visitTemplate(template: t.Template) {
const NG_TEMPLATE_TAG_NAME = 'ng-template';
const templateIndex = this.allocateDataSlot();
if (this.i18n) {
@ -794,13 +794,14 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
];
// find directives matching on a given <ng-template> node
this.matchDirectives('ng-template', template);
this.matchDirectives(NG_TEMPLATE_TAG_NAME, template);
// prepare attributes parameter (including attributes used for directive matching)
const attrsExprs: o.Expression[] = [];
template.attributes.forEach(
(a: t.TextAttribute) => { attrsExprs.push(asLiteral(a.name), asLiteral(a.value)); });
attrsExprs.push(...this.prepareBindingsAttrs(template.inputs, template.outputs));
attrsExprs.push(...this.prepareNonRenderAttrs(
template.inputs, template.outputs, undefined, template.templateAttrs));
parameters.push(this.toAttrsParam(attrsExprs));
// local refs (ex.: <ng-template #foo>)
@ -840,17 +841,12 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// handle property bindings e.g. ɵelementProperty(1, 'ngForOf', ɵbind(ctx.items));
const context = o.variable(CONTEXT_NAME);
template.inputs.forEach(input => {
const value = input.value.visit(this._valueConverter);
this.allocateBindingSlots(value);
this.updateInstruction(templateIndex, template.sourceSpan, R3.elementProperty, () => {
return [
o.literal(templateIndex), o.literal(input.name),
this.convertPropertyBinding(context, value)
];
});
});
this.templatePropertyBindings(template, templateIndex, context, template.templateAttrs);
// Only add normal input/output binding instructions on explicit ng-template elements.
if (template.tagName === NG_TEMPLATE_TAG_NAME) {
// Add the input bindings
this.templatePropertyBindings(template, templateIndex, context, template.inputs);
// Generate listeners for directive output
template.outputs.forEach((outputAst: t.BoundEvent) => {
this.creationInstruction(
@ -858,6 +854,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
this.prepareListenerParameter('ng_template', outputAst, templateIndex));
});
}
}
// These should be handled in the template or element directly.
readonly visitReference = invalid;
@ -949,6 +946,23 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
private bindingContext() { return `${this._bindingContext++}`; }
private templatePropertyBindings(
template: t.Template, templateIndex: number, context: o.ReadVarExpr,
attrs: (t.BoundAttribute|t.TextAttribute)[]) {
attrs.forEach(input => {
if (input instanceof t.BoundAttribute) {
const value = input.value.visit(this._valueConverter);
this.allocateBindingSlots(value);
this.updateInstruction(templateIndex, template.sourceSpan, R3.elementProperty, () => {
return [
o.literal(templateIndex), o.literal(input.name),
this.convertPropertyBinding(context, value)
];
});
}
});
}
// Bindings must only be resolved after all local refs have been visited, so all
// instructions are queued in callbacks that execute once the initial pass has completed.
// Otherwise, we wouldn't be able to support local refs that are defined after their
@ -1051,9 +1065,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
* Note that this function will fully ignore all synthetic (@foo) attribute values
* because those values are intended to always be generated as property instructions.
*/
private prepareBindingsAttrs(
inputs: t.BoundAttribute[], outputs: t.BoundEvent[],
styles?: StylingBuilder): o.Expression[] {
private prepareNonRenderAttrs(
inputs: t.BoundAttribute[], outputs: t.BoundEvent[], styles?: StylingBuilder,
templateAttrs: (t.BoundAttribute|t.TextAttribute)[] = []): o.Expression[] {
const alreadySeen = new Set<string>();
const attrExprs: o.Expression[] = [];
@ -1102,6 +1116,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
}
}
if (templateAttrs.length) {
attrExprs.push(o.literal(core.AttributeMarker.Template));
templateAttrs.forEach(attr => addAttrExpr(attr.name));
}
return attrExprs;
}

View File

@ -173,5 +173,9 @@ export function getAttrsForDirectiveMatching(elOrTpl: t.Element | t.Template):
elOrTpl.inputs.forEach(i => { attributesMap[i.name] = ''; });
elOrTpl.outputs.forEach(o => { attributesMap[o.name] = ''; });
if (elOrTpl instanceof t.Template) {
elOrTpl.templateAttrs.forEach(a => attributesMap[a.name] = '');
}
return attributesMap;
}

View File

@ -32,6 +32,8 @@ class R3AstHumanizer implements t.Visitor<void> {
this.visitAll([
template.attributes,
template.inputs,
template.outputs,
template.templateAttrs,
template.references,
template.variables,
template.children,

View File

@ -17,10 +17,11 @@ import {getComponentDef, getDirectiveDef, getPipeDef} from './definition';
import {NG_ELEMENT_ID} from './fields';
import {DirectiveDef} from './interfaces/definition';
import {NO_PARENT_INJECTOR, NodeInjectorFactory, PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags, TNODE, isFactory} from './interfaces/injector';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, isNameOnlyAttributeMarker} from './interfaces/node';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType} from './interfaces/node';
import {DECLARATION_VIEW, INJECTOR, LView, TData, TVIEW, TView, T_HOST} from './interfaces/view';
import {assertNodeOfPossibleTypes} from './node_assert';
import {getLView, getPreviousOrParentTNode, setTNodeAndViewData} from './state';
import {isNameOnlyAttributeMarker} from './util/attrs_utils';
import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from './util/injector_utils';
import {renderStringify} from './util/misc_utils';
import {findComponentView} from './util/view_traversal_utils';

View File

@ -149,10 +149,6 @@ export const enum AttributeMarker {
Template = 4,
}
export function isNameOnlyAttributeMarker(marker: string | AttributeMarker) {
return marker === AttributeMarker.Bindings || marker === AttributeMarker.Template;
}
/**
* A combination of:
* - attribute names and values

View File

@ -10,9 +10,10 @@ import '../util/ng_dev_mode';
import {assertDefined, assertNotEqual} from '../util/assert';
import {AttributeMarker, TAttributes, TNode, TNodeType, isNameOnlyAttributeMarker, unusedValueExportToPlacateAjd as unused1} from './interfaces/node';
import {AttributeMarker, TAttributes, TNode, TNodeType, unusedValueExportToPlacateAjd as unused1} from './interfaces/node';
import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection';
import {getInitialClassNameValue} from './styling/class_and_style_bindings';
import {isNameOnlyAttributeMarker} from './util/attrs_utils';
const unusedValueToPlacateAjd = unused1 + unused2;
@ -35,7 +36,7 @@ function isCssClassMatching(nodeClassAttrVal: string, cssClassToMatch: string):
/**
* Function that checks whether a given tNode matches tag-based selector and has a valid type.
*
* Matching can be perfomed in 2 modes: projection mode (when we project nodes) and regular
* Matching can be performed in 2 modes: projection mode (when we project nodes) and regular
* directive matching mode. In "projection" mode, we do not need to check types, so if tag name
* matches selector, we declare a match. In "directive matching" mode, we also check whether tNode
* is of expected type:
@ -53,8 +54,10 @@ function hasTagAndTypeMatch(
/**
* A utility function to match an Ivy node static data against a simple CSS selector
*
* @param node static data to match
* @param selector
* @param node static data of the node to match
* @param selector The selector to try matching against the node.
* @param isProjectionMode if `true` we are matching for content projection, otherwise we are doing
* directive matching.
* @returns true if node matches the selector.
*/
export function isNodeMatchingSelector(
@ -64,14 +67,7 @@ export function isNodeMatchingSelector(
const nodeAttrs = tNode.attrs || [];
// Find the index of first attribute that has no value, only a name.
let nameOnlyMarkerIdx = nodeAttrs && nodeAttrs.length;
for (let i = 0; i < nodeAttrs.length; i++) {
const nodeAttr = nodeAttrs[i];
if (isNameOnlyAttributeMarker(nodeAttr)) {
nameOnlyMarkerIdx = i;
break;
}
}
const nameOnlyMarkerIdx = getNameOnlyMarkerIndex(nodeAttrs);
// When processing ":not" selectors, we skip to the next ":not" if the
// current one doesn't match
@ -114,8 +110,11 @@ export function isNodeMatchingSelector(
continue;
}
const isInlineTemplate =
tNode.type == TNodeType.Container && tNode.tagName !== NG_TEMPLATE_SELECTOR;
const attrName = (mode & SelectorFlags.CLASS) ? 'class' : current;
const attrIndexInNode = findAttrIndexInNode(attrName, nodeAttrs);
const attrIndexInNode =
findAttrIndexInNode(attrName, nodeAttrs, isInlineTemplate, isProjectionMode);
if (attrIndexInNode === -1) {
if (isPositive(mode)) return false;
@ -125,12 +124,11 @@ export function isNodeMatchingSelector(
if (selectorAttrValue !== '') {
let nodeAttrValue: string;
const maybeAttrName = nodeAttrs[attrIndexInNode];
if (attrIndexInNode > nameOnlyMarkerIdx) {
nodeAttrValue = '';
} else {
ngDevMode && assertNotEqual(
maybeAttrName, AttributeMarker.NamespaceURI,
nodeAttrs[attrIndexInNode], AttributeMarker.NamespaceURI,
'We do not match directives on namespaced attributes');
nodeAttrValue = nodeAttrs[attrIndexInNode + 1] as string;
}
@ -164,34 +162,64 @@ function readClassValueFromTNode(tNode: TNode): string {
}
/**
* Examines an attribute's definition array from a node to find the index of the
* attribute with the specified name.
* Examines the attribute's definition array for a node to find the index of the
* attribute that matches the given `name`.
*
* NOTE: Will not find namespaced attributes.
* NOTE: This will not match namespaced attributes.
*
* Attribute matching depends upon `isInlineTemplate` and `isProjectionMode`.
* The following table summarizes which types of attributes we attempt to match:
*
* =========================================================================================
* Modes | Normal Attributes | Bindings Attributes | Template Attributes
* =========================================================================================
* Inline + Projection | YES | YES | NO
* -----------------------------------------------------------------------------------------
* Inline + Directive | NO | NO | YES
* -----------------------------------------------------------------------------------------
* Non-inline + Projection | YES | YES | NO
* -----------------------------------------------------------------------------------------
* Non-inline + Directive | YES | YES | NO
* =========================================================================================
*
* @param name the name of the attribute to find
* @param attrs the attribute array to examine
* @param isInlineTemplate true if the node being matched is an inline template (e.g. `*ngFor`)
* rather than a manually expanded template node (e.g `<ng-template>`).
* @param isProjectionMode true if we are matching against content projection otherwise we are
* matching against directives.
*/
function findAttrIndexInNode(name: string, attrs: TAttributes | null): number {
function findAttrIndexInNode(
name: string, attrs: TAttributes | null, isInlineTemplate: boolean,
isProjectionMode: boolean): number {
if (attrs === null) return -1;
let nameOnlyMode = false;
let i = 0;
if (isProjectionMode || !isInlineTemplate) {
let bindingsMode = false;
while (i < attrs.length) {
const maybeAttrName = attrs[i];
if (maybeAttrName === name) {
return i;
} else if (maybeAttrName === AttributeMarker.Bindings) {
bindingsMode = true;
} else if (maybeAttrName === AttributeMarker.Template) {
// We do not care about Template attributes in this scenario.
break;
} else if (maybeAttrName === AttributeMarker.NamespaceURI) {
// NOTE(benlesh): will not find namespaced attributes. This is by design.
// Skip the whole namespaced attribute and value. This is by design.
i += 4;
} else {
if (isNameOnlyAttributeMarker(maybeAttrName)) {
nameOnlyMode = true;
continue;
}
i += nameOnlyMode ? 1 : 2;
// In binding mode there are only names, rather than name-value pairs.
i += bindingsMode ? 1 : 2;
}
}
// We did not match the attribute
return -1;
} else {
return matchTemplateAttribute(attrs, name);
}
}
export function isNodeMatchingSelectorList(
@ -222,8 +250,8 @@ export function getProjectAsAttrValue(tNode: TNode): string|null {
* Checks a given node against matching projection selectors and returns
* selector index (or 0 if none matched).
*
* This function takes into account the ngProjectAs attribute: if present its value will be compared
* to the raw (un-parsed) CSS selector instead of using standard selector matching logic.
* This function takes into account the ngProjectAs attribute: if present its value will be
* compared to the raw (un-parsed) CSS selector instead of using standard selector matching logic.
*/
export function matchingProjectionSelectorIndex(
tNode: TNode, selectors: CssSelectorList[], textSelectors: string[]): number {
@ -239,3 +267,25 @@ export function matchingProjectionSelectorIndex(
}
return 0;
}
function getNameOnlyMarkerIndex(nodeAttrs: TAttributes) {
for (let i = 0; i < nodeAttrs.length; i++) {
const nodeAttr = nodeAttrs[i];
if (isNameOnlyAttributeMarker(nodeAttr)) {
return i;
}
}
return nodeAttrs.length;
}
function matchTemplateAttribute(attrs: TAttributes, name: string): number {
let i = attrs.indexOf(AttributeMarker.Template);
if (i > -1) {
i++;
while (i < attrs.length) {
if (attrs[i] === name) return i;
i++;
}
}
return -1;
}

View File

@ -105,3 +105,14 @@ export function attrsStylingIndexOf(attrs: TAttributes, startIndex: number): num
}
return -1;
}
/**
* Test whether the given value is a marker that indicates that the following
* attribute values in a `TAttributes` array are only the names of attributes,
* and not name-value pairs.
* @param marker The attribute marker to test.
* @returns true if the marker is a "name-only" marker (e.g. `Bindings` or `Template`).
*/
export function isNameOnlyAttributeMarker(marker: string | AttributeMarker) {
return marker === AttributeMarker.Bindings || marker === AttributeMarker.Template;
}

View File

@ -48,7 +48,9 @@ describe('@angular/common integration', () => {
template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) {
elementStart(0, 'ul');
{ template(1, liTemplate, 2, 1, 'li', ['ngForOf', '']); }
{
template(1, liTemplate, 2, 1, 'li', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -111,7 +113,9 @@ describe('@angular/common integration', () => {
template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) {
elementStart(0, 'ul');
{ template(1, liTemplate, 2, 3, 'li', ['ngForOf', '']); }
{
template(1, liTemplate, 2, 3, 'li', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -175,7 +179,8 @@ describe('@angular/common integration', () => {
vars: 1,
template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) {
template(0, ngForTemplate, 1, 0, 'comp', ['ngForOf', '']);
template(
0, ngForTemplate, 1, 0, 'comp', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngForOf', bind(ctx.rows));
@ -247,7 +252,9 @@ describe('@angular/common integration', () => {
}
elementEnd();
elementStart(2, 'ul');
{ template(3, liTemplate, 2, 1, 'li', ['ngForOf', '']); }
{
template(3, liTemplate, 2, 1, 'li', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -302,7 +309,9 @@ describe('@angular/common integration', () => {
template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) {
elementStart(0, 'ul');
{ template(1, liTemplate, 2, 1, 'li', ['ngForOf', '']); }
{
template(1, liTemplate, 2, 1, 'li', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -317,7 +326,9 @@ describe('@angular/common integration', () => {
function liTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'li');
{ template(1, spanTemplate, 2, 3, 'span', ['ngForOf', '']); }
{
template(1, spanTemplate, 2, 3, 'span', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -392,7 +403,7 @@ describe('@angular/common integration', () => {
vars: 1,
template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) {
template(0, divTemplate, 2, 1, 'div', ['ngForOf', '']);
template(0, divTemplate, 2, 1, 'div', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngForOf', bind(ctx.items));
@ -406,7 +417,7 @@ describe('@angular/common integration', () => {
function divTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
{ template(1, pTemplate, 3, 2, 'p', ['ngForOf', '']); }
{ template(1, pTemplate, 3, 2, 'p', [AttributeMarker.Template, 'ngFor', 'ngForOf']); }
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -486,7 +497,7 @@ describe('@angular/common integration', () => {
vars: 1,
template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) {
template(0, divTemplate, 2, 1, 'div', ['ngForOf', '']);
template(0, divTemplate, 2, 1, 'div', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngForOf', bind(ctx.items));
@ -500,7 +511,10 @@ describe('@angular/common integration', () => {
function divTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
{ template(1, innerDivTemplate, 2, 1, 'div', ['ngForOf', '']); }
{
template(
1, innerDivTemplate, 2, 1, 'div', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -512,7 +526,9 @@ describe('@angular/common integration', () => {
function innerDivTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
{ template(1, spanTemplate, 2, 2, 'span', ['ngForOf', '']); }
{
template(1, spanTemplate, 2, 2, 'span', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -654,7 +670,8 @@ describe('@angular/common integration', () => {
vars: 1,
template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) {
template(0, itemTemplate0, 2, 1, 'span', ['ngForOf', '']);
template(
0, itemTemplate0, 2, 1, 'span', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngForOf', bind(ctx.items));
@ -668,7 +685,10 @@ describe('@angular/common integration', () => {
function itemTemplate0(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
{ template(1, itemTemplate1, 2, 1, 'span', ['ngForOf', '']); }
{
template(
1, itemTemplate1, 2, 1, 'span', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -680,7 +700,10 @@ describe('@angular/common integration', () => {
function itemTemplate1(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
{ template(1, itemTemplate2, 2, 1, 'span', ['ngForOf', '']); }
{
template(
1, itemTemplate2, 2, 1, 'span', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -692,7 +715,10 @@ describe('@angular/common integration', () => {
function itemTemplate2(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
{ template(1, itemTemplate3, 2, 1, 'span', ['ngForOf', '']); }
{
template(
1, itemTemplate3, 2, 1, 'span', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -704,7 +730,10 @@ describe('@angular/common integration', () => {
function itemTemplate3(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
{ template(1, itemTemplate4, 2, 1, 'span', ['ngForOf', '']); }
{
template(
1, itemTemplate4, 2, 1, 'span', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -716,7 +745,10 @@ describe('@angular/common integration', () => {
function itemTemplate4(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
{ template(1, itemTemplate5, 2, 1, 'span', ['ngForOf', '']); }
{
template(
1, itemTemplate5, 2, 1, 'span', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -728,7 +760,10 @@ describe('@angular/common integration', () => {
function itemTemplate5(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
{ template(1, itemTemplate6, 2, 1, 'span', ['ngForOf', '']); }
{
template(
1, itemTemplate6, 2, 1, 'span', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -740,7 +775,10 @@ describe('@angular/common integration', () => {
function itemTemplate6(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
{ template(1, itemTemplate7, 2, 1, 'span', ['ngForOf', '']); }
{
template(
1, itemTemplate7, 2, 1, 'span', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -752,7 +790,10 @@ describe('@angular/common integration', () => {
function itemTemplate7(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
{ template(1, itemTemplate8, 2, 10, 'span', ['ngForOf', '']); }
{
template(
1, itemTemplate8, 2, 10, 'span', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -822,8 +863,8 @@ describe('@angular/common integration', () => {
*/
template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) {
template(0, templateOne, 2, 1, 'div', ['ngIf', '']);
template(1, templateTwo, 2, 1, 'div', ['ngIf', '']);
template(0, templateOne, 2, 1, 'div', [AttributeMarker.Template, 'ngIf']);
template(1, templateTwo, 2, 1, 'div', [AttributeMarker.Template, 'ngIf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngIf', bind(ctx.showing));
@ -892,7 +933,7 @@ describe('@angular/common integration', () => {
vars: 1,
template: (rf: RenderFlags, ctx: AppComponent) => {
if (rf & RenderFlags.Create) {
template(0, divTemplate, 2, 1, 'div', ['ngIf', '']);
template(0, divTemplate, 2, 1, 'div', [AttributeMarker.Template, 'ngIf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngIf', bind(ctx.showing));
@ -906,7 +947,7 @@ describe('@angular/common integration', () => {
function divTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
{ template(1, outerDivTemplate, 2, 1, 'div', ['ngIf', '']); }
{ template(1, outerDivTemplate, 2, 1, 'div', [AttributeMarker.Template, 'ngIf']); }
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -918,7 +959,7 @@ describe('@angular/common integration', () => {
function outerDivTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
{ template(1, innerDivTemplate, 2, 1, 'div', ['ngIf', '']); }
{ template(1, innerDivTemplate, 2, 1, 'div', [AttributeMarker.Template, 'ngIf']); }
elementEnd();
}
if (rf & RenderFlags.Update) {

View File

@ -186,7 +186,7 @@ it('should not invoke renderer destroy method for embedded views', () => {
elementStart(0, 'div');
text(1, 'Root view');
elementEnd();
template(2, MyComponent_div_Template_2, 2, 0, null, [AttributeMarker.Bindings, 'ngIf']);
template(2, MyComponent_div_Template_2, 2, 0, 'div', [AttributeMarker.Template, 'ngIf']);
}
if (rf & RenderFlags.Update) {
elementProperty(2, 'ngIf', bind(ctx.visible));
@ -514,8 +514,12 @@ describe('recursive components', () => {
if (rf & RenderFlags.Create) {
text(0);
template(1, IfTemplate, 1, 1, 'ng-if-tree', [AttributeMarker.Bindings, 'ngIf']);
template(2, IfTemplate2, 1, 1, 'ng-if-tree', [AttributeMarker.Bindings, 'ngIf']);
template(
1, IfTemplate, 1, 1, 'ng-if-tree',
[AttributeMarker.Bindings, 'data', AttributeMarker.Template, 'ngIf']);
template(
2, IfTemplate2, 1, 1, 'ng-if-tree',
[AttributeMarker.Bindings, 'data', AttributeMarker.Template, 'ngIf']);
}
if (rf & RenderFlags.Update) {
textBinding(0, bind(ctx.data.value));

View File

@ -945,7 +945,7 @@ describe('content projection', () => {
projectionDef([[['div']]], ['div']);
projection(0);
text(1, 'Before-');
template(2, IfTemplate, 1, 0, '', [AttributeMarker.Bindings, 'ngIf']);
template(2, IfTemplate, 1, 0, 'ng-template', [AttributeMarker.Bindings, 'ngIf']);
text(3, '-After');
}
if (rf & RenderFlags.Update) {
@ -1184,7 +1184,7 @@ describe('content projection', () => {
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
projectionDef();
{ template(0, ForTemplate, 3, 1, undefined, ['ngForOf', '']); }
{ template(0, ForTemplate, 3, 1, 'div', [AttributeMarker.Template, 'ngFor', 'ngForOf']); }
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngForOf', bind(items));
@ -1461,7 +1461,7 @@ describe('content projection', () => {
// <root-comp>
// <ng-container *ngFor="let item of items; last as last">
// <child-comp *ngIf="!last"></child-comp>
// <nested-comp *ngIf="!last"></nested-comp>
// </ng-container>
// </root-comp>
function MyApp_ng_container_1_child_comp_1_Template(rf: RenderFlags, ctx: any) {
@ -1472,7 +1472,9 @@ describe('content projection', () => {
function MyApp_ng_container_1_Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementContainerStart(0);
template(1, MyApp_ng_container_1_child_comp_1_Template, 1, 0, 'nested-comp', [3, 'ngIf']);
template(
1, MyApp_ng_container_1_child_comp_1_Template, 1, 0, 'nested-comp',
[AttributeMarker.Template, 'ngIf']);
elementContainerEnd();
}
if (rf & RenderFlags.Update) {
@ -1495,7 +1497,7 @@ describe('content projection', () => {
elementStart(0, 'root-comp');
template(
1, MyApp_ng_container_1_Template, 2, 1, 'ng-container',
['ngFor', '', 3, 'ngForOf']);
[AttributeMarker.Template, 'ngFor', 'ngForOf']);
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -2089,7 +2091,7 @@ describe('content projection', () => {
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: {value: any}) {
if (rf & RenderFlags.Create) {
elementStart(0, 'child');
{ template(1, IfTemplate, 2, 0, 'div', [AttributeMarker.Bindings, 'ngIf']); }
{ template(1, IfTemplate, 2, 0, 'div', [AttributeMarker.Template, 'ngIf']); }
elementEnd();
}
if (rf & RenderFlags.Update) {

View File

@ -490,7 +490,7 @@ describe('di', () => {
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirB', '']);
{ template(1, IfTemplate, 4, 1, 'div', [AttributeMarker.Bindings, 'ngIf', '']); }
{ template(1, IfTemplate, 4, 1, 'div', [AttributeMarker.Template, 'ngIf']); }
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -1245,7 +1245,10 @@ describe('di', () => {
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirB', '']);
{ template(1, IfTemplate, 1, 0, 'div', ['ngIf', '']); }
{
template(
1, IfTemplate, 1, 0, 'div', ['dirA', '', AttributeMarker.Template, 'ngIf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
@ -1787,7 +1790,7 @@ describe('di', () => {
it('should inject current component ChangeDetectorRef into directives on the same node as components',
() => {
/** <my-comp dir dirSameInstance #dir="dir"></my-comp> {{ dir.value }} */
/** <my-comp dir dirSame #dir="dir"></my-comp> {{ dir.value }} */
const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'my-comp', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
@ -1821,7 +1824,7 @@ describe('di', () => {
consts: 3,
vars: 1,
factory: () => new MyApp(directiveInject(ChangeDetectorRef as any)),
/** <div dir dirSameInstance #dir="dir"> {{ dir.value }} </div> */
/** <div dir dirSame #dir="dir"> {{ dir.value }} </div> */
template: function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
@ -1859,7 +1862,7 @@ describe('di', () => {
factory: () => new MyApp(directiveInject(ChangeDetectorRef as any)),
/**
* <my-comp>
* <div dir dirSameInstance #dir="dir"></div>
* <div dir dirSame #dir="dir"></div>
* </my-comp>
* {{ dir.value }}
*/
@ -1903,7 +1906,7 @@ describe('di', () => {
vars: 0,
/**
* % if (showing) {
* <div dir dirSameInstance #dir="dir"> {{ dir.value }} </div>
* <div dir dirSame #dir="dir"> {{ dir.value }} </div>
* % }
*/
template: function(rf: RenderFlags, ctx: MyApp) {
@ -1967,10 +1970,15 @@ describe('di', () => {
factory: () => new MyApp(directiveInject(ChangeDetectorRef as any)),
consts: 1,
vars: 0,
/** <div *ngIf="showing" dir dirSameInstance #dir="dir"> {{ dir.value }} </div> */
/** <div *ngIf="showing" dir dirSame #dir="dir"> {{ dir.value }} </div> */
template: function(rf: RenderFlags, ctx: MyApp) {
if (rf & RenderFlags.Create) {
template(0, C1, 3, 1, 'div', ['ngIf', 'showing']);
template(
0, C1, 3, 1, 'div',
['dir', '', 'dirSame', '', AttributeMarker.Template, 'ngIf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngIf', bind(ctx.showing));
}
},
directives: directives

View File

@ -208,7 +208,7 @@ describe('directive', () => {
if (rf & RenderFlags.Create) {
template(
0, MyComponent_ng_container_Template_0, 2, 0, 'ng-container',
[AttributeMarker.Bindings, 'ngIf']);
['directiveA', '', AttributeMarker.Template, 'ngIf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngIf', bind(ctx.visible));

View File

@ -7,7 +7,7 @@
*/
import {StaticInjector} from '../../src/di/injector';
import {createInjector} from '../../src/di/r3_injector';
import {ProvidersFeature, RenderFlags, defineComponent, defineDirective, elementContainerEnd, elementContainerStart, getHostElement, i18n, i18nApply, i18nExp} from '../../src/render3/index';
import {AttributeMarker, ProvidersFeature, RenderFlags, defineComponent, defineDirective, elementContainerEnd, elementContainerStart, getHostElement, i18n, i18nApply, i18nExp} from '../../src/render3/index';
import {getComponent, getContext, getDirectives, getInjectionTokens, getInjector, getListeners, getLocalRefs, getRootComponents, getViewComponent, loadLContext} from '../../src/render3/util/discovery_utils';
import {element, elementEnd, elementStart, elementStyling, elementStylingApply, template, bind, elementProperty, text, textBinding, markDirty, listener} from '../../src/render3/instructions';
@ -122,7 +122,7 @@ describe('discovery utils', () => {
if (rf & RenderFlags.Create) {
element(0, 'child');
}
}, 1, 0, 'child', ['ngIf', '']);
}, 1, 0, 'child', ['dirA', AttributeMarker.Template, 'ngIf']);
elementStart(9, 'i18n');
i18n(10, MSG_DIV);
elementEnd();

View File

@ -232,7 +232,7 @@ describe('exports', () => {
if (rf & RenderFlags.Create) {
elementStart(0, 'input', ['value', 'one'], ['outerInput', '']);
elementEnd();
template(2, outerTemplate, 5, 2, 'div', [AttributeMarker.Bindings, 'ngIf']);
template(2, outerTemplate, 5, 2, 'div', [AttributeMarker.Template, 'ngIf']);
}
if (rf & RenderFlags.Update) {
elementProperty(2, 'ngIf', bind(app.outer));
@ -246,7 +246,7 @@ describe('exports', () => {
text(1);
elementStart(2, 'input', ['value', 'two'], ['innerInput', '']);
elementEnd();
template(4, innerTemplate, 2, 2, 'div', [AttributeMarker.Bindings, 'ngIf']);
template(4, innerTemplate, 2, 2, 'div', [AttributeMarker.Template, 'ngIf']);
}
elementEnd();
}

View File

@ -506,7 +506,7 @@ describe('host bindings', () => {
*/
const App = createComponent('parent', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
template(0, NgForTemplate, 2, 0, 'div', ['ngForOf', '']);
template(0, NgForTemplate, 2, 0, 'div', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngForOf', bind(ctx.rows));

View File

@ -603,7 +603,7 @@ describe('Runtime i18n', () => {
if (rf & RenderFlags.Create) {
i18nStart(0, MSG_DIV, 1);
elementStart(1, 'div');
template(2, subTemplate_2, 2, 0, 'span', ['ngIf', '']);
template(2, subTemplate_2, 2, 0, 'span', [AttributeMarker.Template, 'ngIf']);
elementEnd();
i18nEnd();
}
@ -632,7 +632,7 @@ describe('Runtime i18n', () => {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
i18nStart(1, MSG_DIV);
template(2, subTemplate_1, 3, 1, 'div', ['ngIf', '']);
template(2, subTemplate_1, 3, 1, 'div', [AttributeMarker.Template, 'ngIf']);
i18nEnd();
elementEnd();
}
@ -739,7 +739,7 @@ describe('Runtime i18n', () => {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
i18nStart(1, MSG_DIV);
template(2, subTemplate_1, 2, 2, 'span', [3, 'ngIf']);
template(2, subTemplate_1, 2, 2, 'span', [AttributeMarker.Template, 'ngIf']);
i18nEnd();
elementEnd();
}

View File

@ -304,12 +304,13 @@ describe('instructions', () => {
describe('performance counters', () => {
it('should create tViews only once for each nested level', () => {
const _c0 = ['ngFor', '', 'ngForOf', ''];
const _c0 = [AttributeMarker.Template, 'ngFor', 'ngForOf'];
const _c1 = [AttributeMarker.Template, 'ngFor', 'ngForOf'];
function ToDoAppComponent_NgForOf_Template_0(rf: RenderFlags, ctx0: NgForOfContext<any>) {
if (rf & RenderFlags.Create) {
elementStart(0, 'ul');
template(1, ToDoAppComponent_NgForOf_NgForOf_Template_1, 2, 1, 'li', _c0);
template(1, ToDoAppComponent_NgForOf_NgForOf_Template_1, 2, 1, 'li', _c1);
elementEnd();
}
if (rf & RenderFlags.Update) {

View File

@ -721,8 +721,7 @@ describe('render3 integration test', () => {
const TestCmpt =
createComponent('test-cmpt', function(rf: RenderFlags, ctx: {value: any}) {
if (rf & RenderFlags.Create) {
template(
0, ngIfTemplate, 2, 0, 'ng-template', [AttributeMarker.Bindings, 'ngIf']);
template(0, ngIfTemplate, 2, 0, 'ng-template', [AttributeMarker.Bindings, 'ngIf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngIf', bind(ctx.value));

View File

@ -198,7 +198,7 @@ describe('lifecycles', () => {
/** <comp *ngIf="showing"></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
template(0, IfTemplate, 1, 0, 'comp', ['ngIf', '']);
template(0, IfTemplate, 1, 0, 'comp', [AttributeMarker.Template, 'ngIf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngIf', bind(ctx.showing));
@ -2969,8 +2969,7 @@ describe('lifecycles', () => {
function conditionTpl(rf: RenderFlags, ctx: Cmpt) {
if (rf & RenderFlags.Create) {
template(
0, null, 0, 1, 'ng-template', [AttributeMarker.Bindings, 'onDestroyDirective']);
template(0, null, 0, 1, 'ng-template', [AttributeMarker.Bindings, 'onDestroyDirective']);
}
}

View File

@ -87,7 +87,9 @@ describe('array literals', () => {
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
template(0, IfTemplate, 1, 3, 'my-comp', [AttributeMarker.Bindings, 'ngIf']);
template(
0, IfTemplate, 1, 3, 'my-comp',
[AttributeMarker.Bindings, 'names', AttributeMarker.Template, 'ngIf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngIf', bind(ctx.showing));

View File

@ -1392,7 +1392,8 @@ describe('query', () => {
'cmpt',
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
template(0, Cmpt_Template_1, 2, 0, 'ng-template', ['ngIf', '']);
template(
0, Cmpt_Template_1, 2, 0, 'ng-template', [AttributeMarker.Bindings, 'ngIf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngIf', bind(ctx.value));
@ -2207,7 +2208,7 @@ describe('query', () => {
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
template(
0, AppComponent_Template_1, 1, 0, 'div', [AttributeMarker.Bindings, 'someDir']);
0, AppComponent_Template_1, 1, 0, 'div', [AttributeMarker.Template, 'someDir']);
element(1, 'div', null, ['foo', '']);
}
},
@ -2392,7 +2393,7 @@ describe('query', () => {
const AppComponent = createComponent('app-component', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'shallow-comp');
{ template(1, IfTemplate, 2, 0, 'div', [AttributeMarker.Bindings, 'ngIf', '']); }
{ template(1, IfTemplate, 2, 0, 'div', [AttributeMarker.Template, 'ngIf', '']); }
elementEnd();
}
if (rf & RenderFlags.Update) {