fix(ivy): support multiple exportAs (#27996)
Allows for multiple, comma-separated `exportAs` names, similarly to `ViewEngine`. These changes fix FW-708. PR Close #27996
This commit is contained in:
		
							parent
							
								
									b78351cc7e
								
							
						
					
					
						commit
						9277142d54
					
				| @ -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', () => { | ||||
|  | ||||
| @ -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', () => { | ||||
|  | ||||
| @ -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), | ||||
|  | ||||
| @ -151,7 +151,7 @@ export function defineComponent<T>(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<T>(directiveDefinition: | ||||
|    * | ||||
|    * See: {@link Directive.exportAs} | ||||
|    */ | ||||
|   exportAs?: string; | ||||
|   exportAs?: string[]; | ||||
| }) => never; | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -1716,7 +1716,11 @@ function saveNameToExportMap( | ||||
|     index: number, def: DirectiveDef<any>| ComponentDef<any>, | ||||
|     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<any>).template) exportsMap[''] = index; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -121,7 +121,7 @@ export interface DirectiveDef<T> extends BaseDef<T> { | ||||
|   /** | ||||
|    * 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; | ||||
| export const unusedValueExportToPlacateAjd = 1; | ||||
|  | ||||
| @ -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 = '<div multiple-export-as #x="dirX" #y="dirY"></div>'; | ||||
|               TestBed.overrideComponent(MyComp, {set: {template}}); | ||||
|           const template = '<div multiple-export-as #x="dirX" #y="dirY"></div>'; | ||||
|           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', | ||||
|            () => { | ||||
|  | ||||
| @ -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'] | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|  | ||||
| @ -89,12 +89,12 @@ describe('discovery utils', () => { | ||||
|     static ngDirectiveDef = defineDirective({ | ||||
|       type: DirectiveA, | ||||
|       selectors: [['', 'dirA', '']], | ||||
|       exportAs: 'dirA', | ||||
|       exportAs: ['dirA'], | ||||
|       factory: () => new DirectiveA(), | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   const MSG_DIV = `{<7B>0<EFBFBD>, select, 
 | ||||
|   const MSG_DIV = `{<7B>0<EFBFBD>, 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() | ||||
|         }); | ||||
|       } | ||||
|  | ||||
| @ -115,7 +115,7 @@ describe('exports', () => { | ||||
|         type: SomeDir, | ||||
|         selectors: [['', 'someDir', '']], | ||||
|         factory: () => new SomeDir, | ||||
|         exportAs: 'someDir' | ||||
|         exportAs: ['someDir'] | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -382,7 +382,7 @@ describe('elementProperty', () => { | ||||
|         factory: () => myDir = new MyDir(), | ||||
|         inputs: {role: 'role', direction: 'dir'}, | ||||
|         outputs: {changeStream: 'change'}, | ||||
|         exportAs: 'myDir' | ||||
|         exportAs: ['myDir'] | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -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<ElementRef>;
 | ||||
| @ -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<ElementRef>;
 | ||||
|  | ||||
| @ -329,7 +329,7 @@ export function createComponent( | ||||
| } | ||||
| 
 | ||||
| export function createDirective( | ||||
|     name: string, {exportAs}: {exportAs?: string} = {}): DirectiveType<any> { | ||||
|     name: string, {exportAs}: {exportAs?: string[]} = {}): DirectiveType<any> { | ||||
|   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 () => {}; | ||||
|   } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user