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:
Kristiyan Kostadinov 2019-01-10 22:24:32 +01:00 committed by Andrew Kushnir
parent b78351cc7e
commit 9277142d54
14 changed files with 107 additions and 57 deletions

View File

@ -169,7 +169,7 @@ describe('compiler compliance', () => {
}); });
// TODO(https://github.com/angular/angular/issues/24426): We need to support the parser actually // 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', () => { xit('should support namspaced attributes', () => {
const files = { const files = {
app: { app: {
@ -2536,6 +2536,38 @@ describe('compiler compliance', () => {
const source = result.source; const source = result.source;
expectEmit(source, MyAppDefinition, 'Invalid component definition'); 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', () => { describe('inherited base classes', () => {

View File

@ -934,7 +934,25 @@ describe('ngtsc behavioral tests', () => {
env.driveMain(); env.driveMain();
const jsContents = env.getContents('test.js'); 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', () => { it('should generate correct factory stubs for a test module', () => {

View File

@ -117,9 +117,7 @@ function baseDirectiveFields(
definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs)); definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs));
if (meta.exportAs !== null) { if (meta.exportAs !== null) {
// TODO: handle multiple exportAs values (currently only the first is taken). definitionMap.set('exportAs', o.literalArr(meta.exportAs.map(e => o.literal(e))));
const [exportAs] = meta.exportAs;
definitionMap.set('exportAs', o.literal(exportAs));
} }
return {definitionMap, statements: result.statements}; return {definitionMap, statements: result.statements};
@ -614,7 +612,6 @@ function createTypeForDef(meta: R3DirectiveMetadata, typeBase: o.ExternalReferen
return o.expressionType(o.importExpr(typeBase, [ return o.expressionType(o.importExpr(typeBase, [
typeWithParameters(meta.type, meta.typeArgumentCount), typeWithParameters(meta.type, meta.typeArgumentCount),
stringAsType(selectorForType), stringAsType(selectorForType),
// TODO: handle multiple exportAs values (currently only the first is taken).
meta.exportAs !== null ? stringArrayAsType(meta.exportAs) : o.NONE_TYPE, meta.exportAs !== null ? stringArrayAsType(meta.exportAs) : o.NONE_TYPE,
stringMapAsType(meta.inputs), stringMapAsType(meta.inputs),
stringMapAsType(meta.outputs), stringMapAsType(meta.outputs),

View File

@ -151,7 +151,7 @@ export function defineComponent<T>(componentDefinition: {
* *
* See: {@link Directive.exportAs} * See: {@link Directive.exportAs}
*/ */
exportAs?: string; exportAs?: string[];
/** /**
* Template function use for rendering DOM. * Template function use for rendering DOM.
@ -605,7 +605,7 @@ export const defineDirective = defineComponent as any as<T>(directiveDefinition:
* *
* See: {@link Directive.exportAs} * See: {@link Directive.exportAs}
*/ */
exportAs?: string; exportAs?: string[];
}) => never; }) => never;
/** /**

View File

@ -1716,7 +1716,11 @@ function saveNameToExportMap(
index: number, def: DirectiveDef<any>| ComponentDef<any>, index: number, def: DirectiveDef<any>| ComponentDef<any>,
exportsMap: {[key: string]: number} | null) { exportsMap: {[key: string]: number} | null) {
if (exportsMap) { 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; if ((def as ComponentDef<any>).template) exportsMap[''] = index;
} }
} }

View File

@ -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) * 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. * 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 // Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types. // failure based on types.
export const unusedValueExportToPlacateAjd = 1; export const unusedValueExportToPlacateAjd = 1;

View File

@ -477,20 +477,19 @@ function declareTests(config?: {useJit: boolean}) {
.toBeAnInstanceOf(ExportDir); .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', () => {
.it('should assign a directive to a ref when it has multiple exportAs names', () => { TestBed.configureTestingModule(
TestBed.configureTestingModule( {declarations: [MyComp, DirectiveWithMultipleExportAsNames]});
{declarations: [MyComp, DirectiveWithMultipleExportAsNames]});
const template = '<div multiple-export-as #x="dirX" #y="dirY"></div>'; const template = '<div multiple-export-as #x="dirX" #y="dirY"></div>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
expect(fixture.debugElement.children[0].references !['x']) expect(fixture.debugElement.children[0].references !['x'])
.toBeAnInstanceOf(DirectiveWithMultipleExportAsNames); .toBeAnInstanceOf(DirectiveWithMultipleExportAsNames);
expect(fixture.debugElement.children[0].references !['y']) expect(fixture.debugElement.children[0].references !['y'])
.toBeAnInstanceOf(DirectiveWithMultipleExportAsNames); .toBeAnInstanceOf(DirectiveWithMultipleExportAsNames);
}); });
it('should make the assigned component accessible in property bindings, even if they were declared before the component', it('should make the assigned component accessible in property bindings, even if they were declared before the component',
() => { () => {

View File

@ -36,7 +36,7 @@ describe('di', () => {
type: Directive, type: Directive,
selectors: [['', 'dir', '']], selectors: [['', 'dir', '']],
factory: () => new Directive, factory: () => new Directive,
exportAs: 'dir' exportAs: ['dir']
}); });
} }
@ -89,7 +89,7 @@ describe('di', () => {
type: DirC, type: DirC,
selectors: [['', 'dirC', '']], selectors: [['', 'dirC', '']],
factory: () => new DirC(directiveInject(DirA), directiveInject(DirB)), factory: () => new DirC(directiveInject(DirA), directiveInject(DirB)),
exportAs: 'dirC' exportAs: ['dirC']
}); });
} }
@ -427,7 +427,7 @@ describe('di', () => {
type: DirA, type: DirA,
selectors: [['', 'dirA', '']], selectors: [['', 'dirA', '']],
factory: () => new DirA(directiveInject(DirB), directiveInject(ViewContainerRef as any)), factory: () => new DirA(directiveInject(DirB), directiveInject(ViewContainerRef as any)),
exportAs: 'dirA' exportAs: ['dirA']
}); });
} }
@ -1478,7 +1478,7 @@ describe('di', () => {
type: Directive, type: Directive,
selectors: [['', 'dir', '']], selectors: [['', 'dir', '']],
factory: () => dir = new Directive(directiveInject(ElementRef)), factory: () => dir = new Directive(directiveInject(ElementRef)),
exportAs: 'dir' exportAs: ['dir']
}); });
} }
@ -1492,7 +1492,7 @@ describe('di', () => {
selectors: [['', 'dirSame', '']], selectors: [['', 'dirSame', '']],
factory: () => dirSameInstance = new DirectiveSameInstance( factory: () => dirSameInstance = new DirectiveSameInstance(
directiveInject(ElementRef), directiveInject(Directive)), directiveInject(ElementRef), directiveInject(Directive)),
exportAs: 'dirSame' exportAs: ['dirSame']
}); });
} }
@ -1528,7 +1528,7 @@ describe('di', () => {
type: Directive, type: Directive,
selectors: [['', 'dir', '']], selectors: [['', 'dir', '']],
factory: () => dir = new Directive(directiveInject(ElementRef)), factory: () => dir = new Directive(directiveInject(ElementRef)),
exportAs: 'dir' exportAs: ['dir']
}); });
} }
@ -1556,7 +1556,7 @@ describe('di', () => {
type: Directive, type: Directive,
selectors: [['', 'dir', '']], selectors: [['', 'dir', '']],
factory: () => new Directive(directiveInject(TemplateRef as any)), factory: () => new Directive(directiveInject(TemplateRef as any)),
exportAs: 'dir' exportAs: ['dir']
}); });
} }
@ -1571,7 +1571,7 @@ describe('di', () => {
selectors: [['', 'dirSame', '']], selectors: [['', 'dirSame', '']],
factory: () => new DirectiveSameInstance( factory: () => new DirectiveSameInstance(
directiveInject(TemplateRef as any), directiveInject(Directive)), directiveInject(TemplateRef as any), directiveInject(Directive)),
exportAs: 'dirSame' exportAs: ['dirSame']
}); });
} }
@ -1633,7 +1633,7 @@ describe('di', () => {
selectors: [['', 'dir', '']], selectors: [['', 'dir', '']],
factory: () => dir = new OptionalDirective( factory: () => dir = new OptionalDirective(
directiveInject(TemplateRef as any, InjectFlags.Optional)), directiveInject(TemplateRef as any, InjectFlags.Optional)),
exportAs: 'dir' exportAs: ['dir']
}); });
} }
@ -1661,7 +1661,7 @@ describe('di', () => {
type: Directive, type: Directive,
selectors: [['', 'dir', '']], selectors: [['', 'dir', '']],
factory: () => new Directive(directiveInject(ViewContainerRef as any)), factory: () => new Directive(directiveInject(ViewContainerRef as any)),
exportAs: 'dir' exportAs: ['dir']
}); });
} }
@ -1675,7 +1675,7 @@ describe('di', () => {
selectors: [['', 'dirSame', '']], selectors: [['', 'dirSame', '']],
factory: () => new DirectiveSameInstance( factory: () => new DirectiveSameInstance(
directiveInject(ViewContainerRef as any), directiveInject(Directive)), directiveInject(ViewContainerRef as any), directiveInject(Directive)),
exportAs: 'dirSame' exportAs: ['dirSame']
}); });
} }
@ -1737,7 +1737,7 @@ describe('di', () => {
type: Directive, type: Directive,
selectors: [['', 'dir', '']], selectors: [['', 'dir', '']],
factory: () => dir = new Directive(directiveInject(ChangeDetectorRef as any)), factory: () => dir = new Directive(directiveInject(ChangeDetectorRef as any)),
exportAs: 'dir' exportAs: ['dir']
}); });
} }
@ -2223,7 +2223,7 @@ describe('di', () => {
type: ChildDirective, type: ChildDirective,
selectors: [['', 'childDir', '']], selectors: [['', 'childDir', '']],
factory: () => new ChildDirective(directiveInject(ParentDirective)), factory: () => new ChildDirective(directiveInject(ParentDirective)),
exportAs: 'childDir' exportAs: ['childDir']
}); });
} }
@ -2235,7 +2235,7 @@ describe('di', () => {
type: Child2Directive, type: Child2Directive,
factory: () => new Child2Directive( factory: () => new Child2Directive(
directiveInject(ParentDirective), directiveInject(ChildDirective)), directiveInject(ParentDirective), directiveInject(ChildDirective)),
exportAs: 'child2Dir' exportAs: ['child2Dir']
}); });
} }

View File

@ -89,12 +89,12 @@ describe('discovery utils', () => {
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: DirectiveA, type: DirectiveA,
selectors: [['', 'dirA', '']], selectors: [['', 'dirA', '']],
exportAs: 'dirA', exportAs: ['dirA'],
factory: () => new DirectiveA(), factory: () => new DirectiveA(),
}); });
} }
const MSG_DIV = `{<7B>0<EFBFBD>, select, const MSG_DIV = `{<7B>0<EFBFBD>, select,
other {ICU expression} other {ICU expression}
}`; }`;
@ -509,7 +509,7 @@ describe('discovery utils deprecated', () => {
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: MyDir, type: MyDir,
selectors: [['', 'myDir', '']], selectors: [['', 'myDir', '']],
exportAs: 'myDir', exportAs: ['myDir'],
factory: () => new MyDir() factory: () => new MyDir()
}); });
} }

View File

@ -115,7 +115,7 @@ describe('exports', () => {
type: SomeDir, type: SomeDir,
selectors: [['', 'someDir', '']], selectors: [['', 'someDir', '']],
factory: () => new SomeDir, factory: () => new SomeDir,
exportAs: 'someDir' exportAs: ['someDir']
}); });
} }

View File

@ -382,7 +382,7 @@ describe('elementProperty', () => {
factory: () => myDir = new MyDir(), factory: () => myDir = new MyDir(),
inputs: {role: 'role', direction: 'dir'}, inputs: {role: 'role', direction: 'dir'},
outputs: {changeStream: 'change'}, outputs: {changeStream: 'change'},
exportAs: 'myDir' exportAs: ['myDir']
}); });
} }

View File

@ -828,7 +828,7 @@ describe('query', () => {
consts: 0, consts: 0,
vars: 0, vars: 0,
template: (rf: RenderFlags, ctx: Child) => {}, 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', 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; let childInstance;
/** /**
@ -902,8 +902,8 @@ describe('query', () => {
}); });
it('should read all matching directive instances from a given element', () => { it('should read all matching directive instances from a given element', () => {
const Child1 = createDirective('child1', {exportAs: 'child1'}); const Child1 = createDirective('child1', {exportAs: ['child1']});
const Child2 = createDirective('child2', {exportAs: 'child2'}); const Child2 = createDirective('child2', {exportAs: ['child2']});
let child1Instance, child2Instance; let child1Instance, child2Instance;
/** /**
@ -942,7 +942,7 @@ describe('query', () => {
}); });
it('should read multiple locals exporting the same directive from a given element', () => { 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; let childInstance;
/** /**
@ -989,7 +989,7 @@ describe('query', () => {
}); });
it('should match on exported directive name and read a requested token', () => { 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; let div;
/** /**
@ -1024,7 +1024,7 @@ describe('query', () => {
}); });
it('should support reading a mix of ElementRef and directive instances', () => { 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; let childInstance, div;
/** /**
@ -2493,7 +2493,7 @@ describe('query', () => {
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: QueryDirective, type: QueryDirective,
selectors: [['', 'query', '']], selectors: [['', 'query', '']],
exportAs: 'query', exportAs: ['query'],
factory: () => new QueryDirective(), factory: () => new QueryDirective(),
contentQueries: (dirIndex) => { contentQueries: (dirIndex) => {
// @ContentChildren('foo, bar, baz', {descendants: true}) fooBars: // @ContentChildren('foo, bar, baz', {descendants: true}) fooBars:
@ -2557,7 +2557,7 @@ describe('query', () => {
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: QueryDirective, type: QueryDirective,
selectors: [['', 'query', '']], selectors: [['', 'query', '']],
exportAs: 'query', exportAs: ['query'],
factory: () => new QueryDirective(), factory: () => new QueryDirective(),
contentQueries: (dirIndex) => { contentQueries: (dirIndex) => {
// @ContentChildren('foo, bar, baz', {descendants: true}) fooBars: // @ContentChildren('foo, bar, baz', {descendants: true}) fooBars:
@ -2611,7 +2611,7 @@ describe('query', () => {
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: ShallowQueryDirective, type: ShallowQueryDirective,
selectors: [['', 'shallow-query', '']], selectors: [['', 'shallow-query', '']],
exportAs: 'shallow-query', exportAs: ['shallow-query'],
factory: () => new ShallowQueryDirective(), factory: () => new ShallowQueryDirective(),
contentQueries: (dirIndex) => { contentQueries: (dirIndex) => {
// @ContentChildren('foo', {descendants: false}) foos: QueryList<ElementRef>; // @ContentChildren('foo', {descendants: false}) foos: QueryList<ElementRef>;
@ -2631,7 +2631,7 @@ describe('query', () => {
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: DeepQueryDirective, type: DeepQueryDirective,
selectors: [['', 'deep-query', '']], selectors: [['', 'deep-query', '']],
exportAs: 'deep-query', exportAs: ['deep-query'],
factory: () => new DeepQueryDirective(), factory: () => new DeepQueryDirective(),
contentQueries: (dirIndex) => { contentQueries: (dirIndex) => {
// @ContentChildren('foo', {descendants: false}) foos: QueryList<ElementRef>; // @ContentChildren('foo', {descendants: false}) foos: QueryList<ElementRef>;

View File

@ -329,7 +329,7 @@ export function createComponent(
} }
export function createDirective( export function createDirective(
name: string, {exportAs}: {exportAs?: string} = {}): DirectiveType<any> { name: string, {exportAs}: {exportAs?: string[]} = {}): DirectiveType<any> {
return class Directive { return class Directive {
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: Directive, type: Directive,
@ -430,4 +430,4 @@ class MockRenderer implements ProceduralRenderer3 {
listen(target: RNode, eventName: string, callback: (event: any) => boolean | void): () => void { listen(target: RNode, eventName: string, callback: (event: any) => boolean | void): () => void {
return () => {}; return () => {};
} }
} }

View File

@ -230,7 +230,7 @@ function buildElementWithStyling() {
class Comp { class Comp {
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: Comp, type: Comp,
exportAs: 'child', exportAs: ['child'],
selectors: [['child-comp']], selectors: [['child-comp']],
factory: () => new Comp(), factory: () => new Comp(),
consts: 1, consts: 1,
@ -250,7 +250,7 @@ class Comp {
class CompWithStyling { class CompWithStyling {
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: CompWithStyling, type: CompWithStyling,
exportAs: 'child-styled', exportAs: ['child-styled'],
selectors: [['child-styled-comp']], selectors: [['child-styled-comp']],
factory: () => new CompWithStyling(), factory: () => new CompWithStyling(),
consts: 1, consts: 1,