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
// 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', () => {

View File

@ -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', () => {

View File

@ -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),

View File

@ -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;
/**

View File

@ -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;
}
}

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)
*/
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;

View File

@ -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',
() => {

View File

@ -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']
});
}

View File

@ -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()
});
}

View File

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

View File

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

View File

@ -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>;

View File

@ -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 () => {};
}
}
}

View File

@ -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,