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:

committed by
Kara Erickson

parent
e3a401d20c
commit
f535f31d78
@ -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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user