diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts index 74edd68032..21092cb120 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -169,7 +169,7 @@ describe('compiler compliance', () => { }); // TODO(https://github.com/angular/angular/issues/24426): We need to support the parser actually - // building the proper attributes based off of xmlns atttribuates. + // building the proper attributes based off of xmlns attributes. xit('should support namspaced attributes', () => { const files = { app: { @@ -2536,6 +2536,38 @@ describe('compiler compliance', () => { const source = result.source; expectEmit(source, MyAppDefinition, 'Invalid component definition'); }); + + it('should split multiple `exportAs` values into an array', () => { + const files = { + app: { + 'spec.ts': ` + import {Directive, NgModule} from '@angular/core'; + + @Directive({selector: '[some-directive]', exportAs: 'someDir, otherDir'}) + export class SomeDirective {} + + @NgModule({declarations: [SomeDirective, MyComponent]}) + export class MyModule{} + ` + } + }; + + // SomeDirective definition should be: + const SomeDirectiveDefinition = ` + SomeDirective.ngDirectiveDef = $r3$.ɵdefineDirective({ + type: SomeDirective, + selectors: [["", "some-directive", ""]], + factory: function SomeDirective_Factory(t) {return new (t || SomeDirective)(); }, + exportAs: ["someDir", "otherDir"] + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef'); + }); + }); describe('inherited base classes', () => { diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index c2b6272b11..945051348e 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -934,7 +934,25 @@ describe('ngtsc behavioral tests', () => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain(`exportAs: "foo"`); + expect(jsContents).toContain(`exportAs: ["foo"]`); + }); + + it('should generate multiple exportAs declarations', () => { + env.tsconfig(); + env.write('test.ts', ` + import {Component, Directive} from '@angular/core'; + + @Directive({ + selector: '[test]', + exportAs: 'foo, bar', + }) + class Dir {} + `); + + env.driveMain(); + + const jsContents = env.getContents('test.js'); + expect(jsContents).toContain(`exportAs: ["foo", "bar"]`); }); it('should generate correct factory stubs for a test module', () => { diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index c329ccc2f9..0fc19b159b 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -117,9 +117,7 @@ function baseDirectiveFields( definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs)); if (meta.exportAs !== null) { - // TODO: handle multiple exportAs values (currently only the first is taken). - const [exportAs] = meta.exportAs; - definitionMap.set('exportAs', o.literal(exportAs)); + definitionMap.set('exportAs', o.literalArr(meta.exportAs.map(e => o.literal(e)))); } return {definitionMap, statements: result.statements}; @@ -614,7 +612,6 @@ function createTypeForDef(meta: R3DirectiveMetadata, typeBase: o.ExternalReferen return o.expressionType(o.importExpr(typeBase, [ typeWithParameters(meta.type, meta.typeArgumentCount), stringAsType(selectorForType), - // TODO: handle multiple exportAs values (currently only the first is taken). meta.exportAs !== null ? stringArrayAsType(meta.exportAs) : o.NONE_TYPE, stringMapAsType(meta.inputs), stringMapAsType(meta.outputs), diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index a73e0bd9c3..c0e8b5ac65 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -151,7 +151,7 @@ export function defineComponent(componentDefinition: { * * See: {@link Directive.exportAs} */ - exportAs?: string; + exportAs?: string[]; /** * Template function use for rendering DOM. @@ -605,7 +605,7 @@ export const defineDirective = defineComponent as any as(directiveDefinition: * * See: {@link Directive.exportAs} */ - exportAs?: string; + exportAs?: string[]; }) => never; /** diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 5aa343c5fb..2240f90158 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -1716,7 +1716,11 @@ function saveNameToExportMap( index: number, def: DirectiveDef| ComponentDef, exportsMap: {[key: string]: number} | null) { if (exportsMap) { - if (def.exportAs) exportsMap[def.exportAs] = index; + if (def.exportAs) { + for (let i = 0; i < def.exportAs.length; i++) { + exportsMap[def.exportAs[i]] = index; + } + } if ((def as ComponentDef).template) exportsMap[''] = index; } } diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 2fa99bb932..92e0a05dcf 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -121,7 +121,7 @@ export interface DirectiveDef extends BaseDef { /** * Name under which the directive is exported (for use with local references in template) */ - readonly exportAs: string|null; + readonly exportAs: string[]|null; /** * Factory function used to create a new directive instance. @@ -349,4 +349,4 @@ export type PipeTypeList = // Note: This hack is necessary so we don't erroneously get a circular dependency // failure based on types. -export const unusedValueExportToPlacateAjd = 1; \ No newline at end of file +export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/test/linker/integration_spec.ts b/packages/core/test/linker/integration_spec.ts index ae444e8fa9..74726f2bf4 100644 --- a/packages/core/test/linker/integration_spec.ts +++ b/packages/core/test/linker/integration_spec.ts @@ -477,20 +477,19 @@ function declareTests(config?: {useJit: boolean}) { .toBeAnInstanceOf(ExportDir); }); - fixmeIvy('FW-708: Directives with multiple exports are not supported') - .it('should assign a directive to a ref when it has multiple exportAs names', () => { - TestBed.configureTestingModule( - {declarations: [MyComp, DirectiveWithMultipleExportAsNames]}); + 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 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); - }); + 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', () => { diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 3ac7c644d2..1cf56f015f 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -36,7 +36,7 @@ describe('di', () => { type: Directive, selectors: [['', 'dir', '']], factory: () => new Directive, - exportAs: 'dir' + exportAs: ['dir'] }); } @@ -89,7 +89,7 @@ describe('di', () => { type: DirC, selectors: [['', 'dirC', '']], factory: () => new DirC(directiveInject(DirA), directiveInject(DirB)), - exportAs: 'dirC' + exportAs: ['dirC'] }); } @@ -427,7 +427,7 @@ describe('di', () => { type: DirA, selectors: [['', 'dirA', '']], factory: () => new DirA(directiveInject(DirB), directiveInject(ViewContainerRef as any)), - exportAs: 'dirA' + exportAs: ['dirA'] }); } @@ -1478,7 +1478,7 @@ describe('di', () => { type: Directive, selectors: [['', 'dir', '']], factory: () => dir = new Directive(directiveInject(ElementRef)), - exportAs: 'dir' + exportAs: ['dir'] }); } @@ -1492,7 +1492,7 @@ describe('di', () => { selectors: [['', 'dirSame', '']], factory: () => dirSameInstance = new DirectiveSameInstance( directiveInject(ElementRef), directiveInject(Directive)), - exportAs: 'dirSame' + exportAs: ['dirSame'] }); } @@ -1528,7 +1528,7 @@ describe('di', () => { type: Directive, selectors: [['', 'dir', '']], factory: () => dir = new Directive(directiveInject(ElementRef)), - exportAs: 'dir' + exportAs: ['dir'] }); } @@ -1556,7 +1556,7 @@ describe('di', () => { type: Directive, selectors: [['', 'dir', '']], factory: () => new Directive(directiveInject(TemplateRef as any)), - exportAs: 'dir' + exportAs: ['dir'] }); } @@ -1571,7 +1571,7 @@ describe('di', () => { selectors: [['', 'dirSame', '']], factory: () => new DirectiveSameInstance( directiveInject(TemplateRef as any), directiveInject(Directive)), - exportAs: 'dirSame' + exportAs: ['dirSame'] }); } @@ -1633,7 +1633,7 @@ describe('di', () => { selectors: [['', 'dir', '']], factory: () => dir = new OptionalDirective( directiveInject(TemplateRef as any, InjectFlags.Optional)), - exportAs: 'dir' + exportAs: ['dir'] }); } @@ -1661,7 +1661,7 @@ describe('di', () => { type: Directive, selectors: [['', 'dir', '']], factory: () => new Directive(directiveInject(ViewContainerRef as any)), - exportAs: 'dir' + exportAs: ['dir'] }); } @@ -1675,7 +1675,7 @@ describe('di', () => { selectors: [['', 'dirSame', '']], factory: () => new DirectiveSameInstance( directiveInject(ViewContainerRef as any), directiveInject(Directive)), - exportAs: 'dirSame' + exportAs: ['dirSame'] }); } @@ -1737,7 +1737,7 @@ describe('di', () => { type: Directive, selectors: [['', 'dir', '']], factory: () => dir = new Directive(directiveInject(ChangeDetectorRef as any)), - exportAs: 'dir' + exportAs: ['dir'] }); } @@ -2223,7 +2223,7 @@ describe('di', () => { type: ChildDirective, selectors: [['', 'childDir', '']], factory: () => new ChildDirective(directiveInject(ParentDirective)), - exportAs: 'childDir' + exportAs: ['childDir'] }); } @@ -2235,7 +2235,7 @@ describe('di', () => { type: Child2Directive, factory: () => new Child2Directive( directiveInject(ParentDirective), directiveInject(ChildDirective)), - exportAs: 'child2Dir' + exportAs: ['child2Dir'] }); } diff --git a/packages/core/test/render3/discovery_utils_spec.ts b/packages/core/test/render3/discovery_utils_spec.ts index f321ce6ead..3260f9c01f 100644 --- a/packages/core/test/render3/discovery_utils_spec.ts +++ b/packages/core/test/render3/discovery_utils_spec.ts @@ -89,12 +89,12 @@ describe('discovery utils', () => { static ngDirectiveDef = defineDirective({ type: DirectiveA, selectors: [['', 'dirA', '']], - exportAs: 'dirA', + exportAs: ['dirA'], factory: () => new DirectiveA(), }); } - const MSG_DIV = `{�0�, select, + const MSG_DIV = `{�0�, select, other {ICU expression} }`; @@ -509,7 +509,7 @@ describe('discovery utils deprecated', () => { static ngDirectiveDef = defineDirective({ type: MyDir, selectors: [['', 'myDir', '']], - exportAs: 'myDir', + exportAs: ['myDir'], factory: () => new MyDir() }); } diff --git a/packages/core/test/render3/exports_spec.ts b/packages/core/test/render3/exports_spec.ts index 847ea9b30b..da2928230c 100644 --- a/packages/core/test/render3/exports_spec.ts +++ b/packages/core/test/render3/exports_spec.ts @@ -115,7 +115,7 @@ describe('exports', () => { type: SomeDir, selectors: [['', 'someDir', '']], factory: () => new SomeDir, - exportAs: 'someDir' + exportAs: ['someDir'] }); } diff --git a/packages/core/test/render3/properties_spec.ts b/packages/core/test/render3/properties_spec.ts index 8a13ec371a..808901d036 100644 --- a/packages/core/test/render3/properties_spec.ts +++ b/packages/core/test/render3/properties_spec.ts @@ -382,7 +382,7 @@ describe('elementProperty', () => { factory: () => myDir = new MyDir(), inputs: {role: 'role', direction: 'dir'}, outputs: {changeStream: 'change'}, - exportAs: 'myDir' + exportAs: ['myDir'] }); } diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index 505b524a4b..d5b571c795 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -828,7 +828,7 @@ describe('query', () => { consts: 0, vars: 0, template: (rf: RenderFlags, ctx: Child) => {}, - exportAs: 'child' + exportAs: ['child'] }); } @@ -864,7 +864,7 @@ describe('query', () => { it('should read directive instance if element queried for has an exported directive with a matching name', () => { - const Child = createDirective('child', {exportAs: 'child'}); + const Child = createDirective('child', {exportAs: ['child']}); let childInstance; /** @@ -902,8 +902,8 @@ describe('query', () => { }); it('should read all matching directive instances from a given element', () => { - const Child1 = createDirective('child1', {exportAs: 'child1'}); - const Child2 = createDirective('child2', {exportAs: 'child2'}); + const Child1 = createDirective('child1', {exportAs: ['child1']}); + const Child2 = createDirective('child2', {exportAs: ['child2']}); let child1Instance, child2Instance; /** @@ -942,7 +942,7 @@ describe('query', () => { }); it('should read multiple locals exporting the same directive from a given element', () => { - const Child = createDirective('child', {exportAs: 'child'}); + const Child = createDirective('child', {exportAs: ['child']}); let childInstance; /** @@ -989,7 +989,7 @@ describe('query', () => { }); it('should match on exported directive name and read a requested token', () => { - const Child = createDirective('child', {exportAs: 'child'}); + const Child = createDirective('child', {exportAs: ['child']}); let div; /** @@ -1024,7 +1024,7 @@ describe('query', () => { }); it('should support reading a mix of ElementRef and directive instances', () => { - const Child = createDirective('child', {exportAs: 'child'}); + const Child = createDirective('child', {exportAs: ['child']}); let childInstance, div; /** @@ -2493,7 +2493,7 @@ describe('query', () => { static ngDirectiveDef = defineDirective({ type: QueryDirective, selectors: [['', 'query', '']], - exportAs: 'query', + exportAs: ['query'], factory: () => new QueryDirective(), contentQueries: (dirIndex) => { // @ContentChildren('foo, bar, baz', {descendants: true}) fooBars: @@ -2557,7 +2557,7 @@ describe('query', () => { static ngDirectiveDef = defineDirective({ type: QueryDirective, selectors: [['', 'query', '']], - exportAs: 'query', + exportAs: ['query'], factory: () => new QueryDirective(), contentQueries: (dirIndex) => { // @ContentChildren('foo, bar, baz', {descendants: true}) fooBars: @@ -2611,7 +2611,7 @@ describe('query', () => { static ngDirectiveDef = defineDirective({ type: ShallowQueryDirective, selectors: [['', 'shallow-query', '']], - exportAs: 'shallow-query', + exportAs: ['shallow-query'], factory: () => new ShallowQueryDirective(), contentQueries: (dirIndex) => { // @ContentChildren('foo', {descendants: false}) foos: QueryList; @@ -2631,7 +2631,7 @@ describe('query', () => { static ngDirectiveDef = defineDirective({ type: DeepQueryDirective, selectors: [['', 'deep-query', '']], - exportAs: 'deep-query', + exportAs: ['deep-query'], factory: () => new DeepQueryDirective(), contentQueries: (dirIndex) => { // @ContentChildren('foo', {descendants: false}) foos: QueryList; diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index ecc3a52c80..51d64e2906 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -329,7 +329,7 @@ export function createComponent( } export function createDirective( - name: string, {exportAs}: {exportAs?: string} = {}): DirectiveType { + name: string, {exportAs}: {exportAs?: string[]} = {}): DirectiveType { return class Directive { static ngDirectiveDef = defineDirective({ type: Directive, @@ -430,4 +430,4 @@ class MockRenderer implements ProceduralRenderer3 { listen(target: RNode, eventName: string, callback: (event: any) => boolean | void): () => void { return () => {}; } -} \ No newline at end of file +} diff --git a/packages/core/test/render3/styling/players_spec.ts b/packages/core/test/render3/styling/players_spec.ts index 9739d437c8..bd2b454cd0 100644 --- a/packages/core/test/render3/styling/players_spec.ts +++ b/packages/core/test/render3/styling/players_spec.ts @@ -230,7 +230,7 @@ function buildElementWithStyling() { class Comp { static ngComponentDef = defineComponent({ type: Comp, - exportAs: 'child', + exportAs: ['child'], selectors: [['child-comp']], factory: () => new Comp(), consts: 1, @@ -250,7 +250,7 @@ class Comp { class CompWithStyling { static ngComponentDef = defineComponent({ type: CompWithStyling, - exportAs: 'child-styled', + exportAs: ['child-styled'], selectors: [['child-styled-comp']], factory: () => new CompWithStyling(), consts: 1,