feat(Compiler): Allow overriding the projection selector
fixes #6303 BREAKING CHANGE: For static content projection, elements with *-directives are now matched against the element itself vs the template before. <p *ngIf="condition" foo></p> Before: // Use the implicit template for projection <ng-content select="template"></ng-content> After: // Use the actual element for projection <ng-content select="p[foo]"></ng-content> Closes #7742
This commit is contained in:
@ -264,29 +264,40 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directives);
|
||||
var children = htmlVisitAll(preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this,
|
||||
element.children, Component.create(directives));
|
||||
var elementNgContentIndex =
|
||||
hasInlineTemplates ? null : component.findNgContentIndex(elementCssSelector);
|
||||
|
||||
// Override the actual selector when the `ngProjectAs` attribute is provided
|
||||
var projectionSelector = isPresent(preparsedElement.projectAs) ?
|
||||
CssSelector.parse(preparsedElement.projectAs)[0] :
|
||||
elementCssSelector;
|
||||
var ngContentIndex = component.findNgContentIndex(projectionSelector);
|
||||
var parsedElement;
|
||||
|
||||
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
|
||||
if (isPresent(element.children) && element.children.length > 0) {
|
||||
this._reportError(
|
||||
`<ng-content> element cannot have content. <ng-content> must be immediately followed by </ng-content>`,
|
||||
element.sourceSpan);
|
||||
}
|
||||
parsedElement =
|
||||
new NgContentAst(this.ngContentCount++, elementNgContentIndex, element.sourceSpan);
|
||||
|
||||
parsedElement = new NgContentAst(
|
||||
this.ngContentCount++, hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
|
||||
} else if (isTemplateElement) {
|
||||
this._assertAllEventsPublishedByDirectives(directives, events);
|
||||
this._assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps,
|
||||
element.sourceSpan);
|
||||
parsedElement = new EmbeddedTemplateAst(attrs, events, vars, directives, children,
|
||||
elementNgContentIndex, element.sourceSpan);
|
||||
|
||||
parsedElement =
|
||||
new EmbeddedTemplateAst(attrs, events, vars, directives, children,
|
||||
hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
|
||||
} else {
|
||||
this._assertOnlyOneComponent(directives, element.sourceSpan);
|
||||
var elementExportAsVars = vars.filter(varAst => varAst.value.length === 0);
|
||||
let ngContentIndex =
|
||||
hasInlineTemplates ? null : component.findNgContentIndex(projectionSelector);
|
||||
|
||||
parsedElement =
|
||||
new ElementAst(nodeName, attrs, elementProps, events, elementExportAsVars, directives,
|
||||
children, elementNgContentIndex, element.sourceSpan);
|
||||
children, hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
|
||||
}
|
||||
if (hasInlineTemplates) {
|
||||
var templateCssSelector = createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
|
||||
@ -297,9 +308,9 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
element.name, templateElementOrDirectiveProps, templateDirectives);
|
||||
this._assertNoComponentsNorElementBindingsOnTemplate(templateDirectives, templateElementProps,
|
||||
element.sourceSpan);
|
||||
parsedElement = new EmbeddedTemplateAst(
|
||||
[], [], templateVars, templateDirectives, [parsedElement],
|
||||
component.findNgContentIndex(templateCssSelector), element.sourceSpan);
|
||||
|
||||
parsedElement = new EmbeddedTemplateAst([], [], templateVars, templateDirectives,
|
||||
[parsedElement], ngContentIndex, element.sourceSpan);
|
||||
}
|
||||
return parsedElement;
|
||||
}
|
||||
|
@ -11,12 +11,14 @@ const LINK_STYLE_REL_VALUE = 'stylesheet';
|
||||
const STYLE_ELEMENT = 'style';
|
||||
const SCRIPT_ELEMENT = 'script';
|
||||
const NG_NON_BINDABLE_ATTR = 'ngNonBindable';
|
||||
const NG_PROJECT_AS = 'ngProjectAs';
|
||||
|
||||
export function preparseElement(ast: HtmlElementAst): PreparsedElement {
|
||||
var selectAttr = null;
|
||||
var hrefAttr = null;
|
||||
var relAttr = null;
|
||||
var nonBindable = false;
|
||||
var projectAs: string = null;
|
||||
ast.attrs.forEach(attr => {
|
||||
let lcAttrName = attr.name.toLowerCase();
|
||||
if (lcAttrName == NG_CONTENT_SELECT_ATTR) {
|
||||
@ -27,6 +29,10 @@ export function preparseElement(ast: HtmlElementAst): PreparsedElement {
|
||||
relAttr = attr.value;
|
||||
} else if (attr.name == NG_NON_BINDABLE_ATTR) {
|
||||
nonBindable = true;
|
||||
} else if (attr.name == NG_PROJECT_AS) {
|
||||
if (attr.value.length > 0) {
|
||||
projectAs = attr.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
selectAttr = normalizeNgContentSelect(selectAttr);
|
||||
@ -41,7 +47,7 @@ export function preparseElement(ast: HtmlElementAst): PreparsedElement {
|
||||
} else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) {
|
||||
type = PreparsedElementType.STYLESHEET;
|
||||
}
|
||||
return new PreparsedElement(type, selectAttr, hrefAttr, nonBindable);
|
||||
return new PreparsedElement(type, selectAttr, hrefAttr, nonBindable, projectAs);
|
||||
}
|
||||
|
||||
export enum PreparsedElementType {
|
||||
@ -54,7 +60,7 @@ export enum PreparsedElementType {
|
||||
|
||||
export class PreparsedElement {
|
||||
constructor(public type: PreparsedElementType, public selectAttr: string, public hrefAttr: string,
|
||||
public nonBindable: boolean) {}
|
||||
public nonBindable: boolean, public projectAs: string) {}
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user