diff --git a/modules/angular2/src/compiler/template_parser.ts b/modules/angular2/src/compiler/template_parser.ts index 875e168999..af05bb5e2e 100644 --- a/modules/angular2/src/compiler/template_parser.ts +++ b/modules/angular2/src/compiler/template_parser.ts @@ -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( ` element cannot have content. must be immediately followed by `, 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; } diff --git a/modules/angular2/src/compiler/template_preparser.ts b/modules/angular2/src/compiler/template_preparser.ts index 6c2948d013..020bb47b98 100644 --- a/modules/angular2/src/compiler/template_preparser.ts +++ b/modules/angular2/src/compiler/template_preparser.ts @@ -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) {} } diff --git a/modules/angular2/test/compiler/template_parser_spec.ts b/modules/angular2/test/compiler/template_parser_spec.ts index f473e47a98..b812af8f49 100644 --- a/modules/angular2/test/compiler/template_parser_spec.ts +++ b/modules/angular2/test/compiler/template_parser_spec.ts @@ -686,6 +686,39 @@ There is no directive with "exportAs" set to "dirA" ("
]#a="dirA">< [createComp('div', ['*'])]))) .toEqual([['div', null], ['#text({{hello}})', 0], ['span', 0]]); }); + + it('should match the element when there is an inline template', () => { + expect(humanizeContentProjection( + parse('
', [createComp('div', ['a', 'b']), ngIf]))) + .toEqual([['div', null], ['template', 1], ['b', null]]); + }); + + describe('ngProjectAs', () => { + it('should override elements', () => { + expect(humanizeContentProjection( + parse('
', [createComp('div', ['a', 'b'])]))) + .toEqual([['div', null], ['a', 1]]); + }); + + it('should override ', () => { + expect(humanizeContentProjection( + parse('
', + [createComp('div', ['ng-content', 'b'])]))) + .toEqual([['div', null], ['ng-content', 1]]); + }); + + it('should override