diff --git a/packages/compiler/src/template_parser/template_parser.ts b/packages/compiler/src/template_parser/template_parser.ts
index ae5dab1455..b52298b211 100644
--- a/packages/compiler/src/template_parser/template_parser.ts
+++ b/packages/compiler/src/template_parser/template_parser.ts
@@ -580,7 +580,7 @@ class TemplateParseVisitor implements html.Visitor {
directive.inputs, props, directiveProperties, targetBoundDirectivePropNames);
elementOrDirectiveRefs.forEach((elOrDirRef) => {
if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
- (directive.exportAs == elOrDirRef.value)) {
+ (elOrDirRef.isReferenceToDirective(directive))) {
targetReferences.push(new ReferenceAst(
elOrDirRef.name, createTokenForReference(directive.type.reference),
elOrDirRef.sourceSpan));
@@ -805,8 +805,25 @@ class NonBindableVisitor implements html.Visitor {
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any { return expansionCase; }
}
+/**
+ * A reference to an element or directive in a template. E.g., the reference in this template:
+ *
+ *
+ *
+ * would be {name: 'myMenu', value: 'coolMenu', sourceSpan: ...}
+ */
class ElementOrDirectiveRef {
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
+
+ /** Gets whether this is a reference to the given directive. */
+ isReferenceToDirective(directive: CompileDirectiveSummary) {
+ return splitExportAs(directive.exportAs).indexOf(this.value) !== -1;
+ }
+}
+
+/** Splits a raw, potentially comma-delimted `exportAs` value into an array of names. */
+function splitExportAs(exportAs: string | null): string[] {
+ return exportAs ? exportAs.split(',').map(e => e.trim()) : [];
}
export function splitClasses(classAttrValue: string): string[] {
diff --git a/packages/compiler/test/template_parser/template_parser_spec.ts b/packages/compiler/test/template_parser/template_parser_spec.ts
index 7ffb4dfe58..7937bd7cbd 100644
--- a/packages/compiler/test/template_parser/template_parser_spec.ts
+++ b/packages/compiler/test/template_parser/template_parser_spec.ts
@@ -1209,6 +1209,24 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("
{
+ const pizzaTestDirective =
+ compileDirectiveMetadataCreate({
+ selector: 'pizza-test',
+ type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'Pizza'}}),
+ exportAs: 'pizza, cheeseSauceBread'
+ }).toSummary();
+
+ const template = '';
+
+ expect(humanizeTplAst(parse(template, [pizzaTestDirective]))).toEqual([
+ [ElementAst, 'pizza-test'],
+ [ReferenceAst, 'food', createTokenForReference(pizzaTestDirective.type.reference)],
+ [ReferenceAst, 'yum', createTokenForReference(pizzaTestDirective.type.reference)],
+ [DirectiveAst, pizzaTestDirective],
+ ]);
+ });
+
it('should report references with values that dont match a directive as errors', () => {
expect(() => parse('', [])).toThrowError(`Template parse errors:
There is no directive with "exportAs" set to "dirA" ("]#a="dirA">
"): TestComp@0:5`);
@@ -1231,6 +1249,31 @@ Reference "#a" is defined several times ("]#a>
});
+ it('should report duplicate reference names when using mutliple exportAs names', () => {
+ const pizzaDirective =
+ compileDirectiveMetadataCreate({
+ selector: '[dessert-pizza]',
+ type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'Pizza'}}),
+ exportAs: 'dessertPizza, chocolate'
+ }).toSummary();
+
+ const chocolateDirective =
+ compileDirectiveMetadataCreate({
+ selector: '[chocolate]',
+ type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'Chocolate'}}),
+ exportAs: 'chocolate'
+ }).toSummary();
+
+ const template = '';
+ const compileTemplate = () => parse(template, [pizzaDirective, chocolateDirective]);
+ const duplicateReferenceError = 'Template parse errors:\n' +
+ 'Reference "#snack" is defined several times ' +
+ '("]#snack="chocolate">
")' +
+ ': TestComp@0:29';
+
+ expect(compileTemplate).toThrowError(duplicateReferenceError);
+ });
+
it('should not throw error when there is same reference name in different templates',
() => {
expect(() => parse('OK
', []))
diff --git a/packages/core/src/metadata/directives.ts b/packages/core/src/metadata/directives.ts
index 8ae0808f67..a8847605ae 100644
--- a/packages/core/src/metadata/directives.ts
+++ b/packages/core/src/metadata/directives.ts
@@ -54,7 +54,8 @@ export interface DirectiveDecorator {
*
* **Metadata Properties:**
*
- * * **exportAs** - name under which the component instance is exported in a template
+ * * **exportAs** - name under which the component instance is exported in a template. Can be
+ * given a single name or a comma-delimited list of names.
* * **host** - map of class property to host element bindings for events, properties and
* attributes
* * **inputs** - list of class property names to data-bind as component inputs
diff --git a/packages/core/test/linker/integration_spec.ts b/packages/core/test/linker/integration_spec.ts
index 5491c1c131..573a4d40d2 100644
--- a/packages/core/test/linker/integration_spec.ts
+++ b/packages/core/test/linker/integration_spec.ts
@@ -469,6 +469,20 @@ function declareTests({useJit}: {useJit: boolean}) {
.toBeAnInstanceOf(ExportDir);
});
+ it('should assign a directive to a ref when it has multiple exportAs names', () => {
+ TestBed.configureTestingModule(
+ {declarations: [MyComp, DirectiveWithMultipleExportAsNames]});
+
+ const template = '';
+ TestBed.overrideComponent(MyComp, {set: {template}});
+
+ const fixture = TestBed.createComponent(MyComp);
+ expect(fixture.debugElement.children[0].references !['x'])
+ .toBeAnInstanceOf(DirectiveWithMultipleExportAsNames);
+ expect(fixture.debugElement.children[0].references !['y'])
+ .toBeAnInstanceOf(DirectiveWithMultipleExportAsNames);
+ });
+
it('should make the assigned component accessible in property bindings, even if they were declared before the component',
() => {
TestBed.configureTestingModule({declarations: [MyComp, ChildComp]});
@@ -2442,6 +2456,10 @@ class SomeImperativeViewport {
class ExportDir {
}
+@Directive({selector: '[multiple-export-as]', exportAs: 'dirX, dirY'})
+export class DirectiveWithMultipleExportAsNames {
+}
+
@Component({selector: 'comp'})
class ComponentWithoutView {
}