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…
Reference in New Issue