feat(compiler): allow multiple exportAs names
This change allows users to specify multiple exportAs names for a directive by giving a comma-delimited list inside the string. The primary motivation for this change is to allow these names to be changed in a backwards compatible way.
This commit is contained in:
parent
0d45828460
commit
3a500981ef
|
@ -580,7 +580,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||||
directive.inputs, props, directiveProperties, targetBoundDirectivePropNames);
|
directive.inputs, props, directiveProperties, targetBoundDirectivePropNames);
|
||||||
elementOrDirectiveRefs.forEach((elOrDirRef) => {
|
elementOrDirectiveRefs.forEach((elOrDirRef) => {
|
||||||
if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
|
if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
|
||||||
(directive.exportAs == elOrDirRef.value)) {
|
(elOrDirRef.isReferenceToDirective(directive))) {
|
||||||
targetReferences.push(new ReferenceAst(
|
targetReferences.push(new ReferenceAst(
|
||||||
elOrDirRef.name, createTokenForReference(directive.type.reference),
|
elOrDirRef.name, createTokenForReference(directive.type.reference),
|
||||||
elOrDirRef.sourceSpan));
|
elOrDirRef.sourceSpan));
|
||||||
|
@ -805,8 +805,25 @@ class NonBindableVisitor implements html.Visitor {
|
||||||
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any { return expansionCase; }
|
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any { return expansionCase; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to an element or directive in a template. E.g., the reference in this template:
|
||||||
|
*
|
||||||
|
* <div #myMenu="coolMenu">
|
||||||
|
*
|
||||||
|
* would be {name: 'myMenu', value: 'coolMenu', sourceSpan: ...}
|
||||||
|
*/
|
||||||
class ElementOrDirectiveRef {
|
class ElementOrDirectiveRef {
|
||||||
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||||
|
|
||||||
|
/** Gets whether this is a reference to the given directive. */
|
||||||
|
isReferenceToDirective(directive: CompileDirectiveSummary) {
|
||||||
|
return splitExportAs(directive.exportAs).indexOf(this.value) !== -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Splits a raw, potentially comma-delimted `exportAs` value into an array of names. */
|
||||||
|
function splitExportAs(exportAs: string | null): string[] {
|
||||||
|
return exportAs ? exportAs.split(',').map(e => e.trim()) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function splitClasses(classAttrValue: string): string[] {
|
export function splitClasses(classAttrValue: string): string[] {
|
||||||
|
|
|
@ -1209,6 +1209,24 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should assign references to directives via exportAs with multiple names', () => {
|
||||||
|
const pizzaTestDirective =
|
||||||
|
compileDirectiveMetadataCreate({
|
||||||
|
selector: 'pizza-test',
|
||||||
|
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'Pizza'}}),
|
||||||
|
exportAs: 'pizza, cheeseSauceBread'
|
||||||
|
}).toSummary();
|
||||||
|
|
||||||
|
const template = '<pizza-test #food="pizza" #yum="cheeseSauceBread"></pizza-test>';
|
||||||
|
|
||||||
|
expect(humanizeTplAst(parse(template, [pizzaTestDirective]))).toEqual([
|
||||||
|
[ElementAst, 'pizza-test'],
|
||||||
|
[ReferenceAst, 'food', createTokenForReference(pizzaTestDirective.type.reference)],
|
||||||
|
[ReferenceAst, 'yum', createTokenForReference(pizzaTestDirective.type.reference)],
|
||||||
|
[DirectiveAst, pizzaTestDirective],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should report references with values that dont match a directive as errors', () => {
|
it('should report references with values that dont match a directive as errors', () => {
|
||||||
expect(() => parse('<div #a="dirA"></div>', [])).toThrowError(`Template parse errors:
|
expect(() => parse('<div #a="dirA"></div>', [])).toThrowError(`Template parse errors:
|
||||||
There is no directive with "exportAs" set to "dirA" ("<div [ERROR ->]#a="dirA"></div>"): TestComp@0:5`);
|
There is no directive with "exportAs" set to "dirA" ("<div [ERROR ->]#a="dirA"></div>"): TestComp@0:5`);
|
||||||
|
@ -1231,6 +1249,31 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should report duplicate reference names when using mutliple exportAs names', () => {
|
||||||
|
const pizzaDirective =
|
||||||
|
compileDirectiveMetadataCreate({
|
||||||
|
selector: '[dessert-pizza]',
|
||||||
|
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'Pizza'}}),
|
||||||
|
exportAs: 'dessertPizza, chocolate'
|
||||||
|
}).toSummary();
|
||||||
|
|
||||||
|
const chocolateDirective =
|
||||||
|
compileDirectiveMetadataCreate({
|
||||||
|
selector: '[chocolate]',
|
||||||
|
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'Chocolate'}}),
|
||||||
|
exportAs: 'chocolate'
|
||||||
|
}).toSummary();
|
||||||
|
|
||||||
|
const template = '<div dessert-pizza chocolate #snack="chocolate"></div>';
|
||||||
|
const compileTemplate = () => parse(template, [pizzaDirective, chocolateDirective]);
|
||||||
|
const duplicateReferenceError = 'Template parse errors:\n' +
|
||||||
|
'Reference "#snack" is defined several times ' +
|
||||||
|
'("<div dessert-pizza chocolate [ERROR ->]#snack="chocolate"></div>")' +
|
||||||
|
': TestComp@0:29';
|
||||||
|
|
||||||
|
expect(compileTemplate).toThrowError(duplicateReferenceError);
|
||||||
|
});
|
||||||
|
|
||||||
it('should not throw error when there is same reference name in different templates',
|
it('should not throw error when there is same reference name in different templates',
|
||||||
() => {
|
() => {
|
||||||
expect(() => parse('<div #a><template #a><span>OK</span></template></div>', []))
|
expect(() => parse('<div #a><template #a><span>OK</span></template></div>', []))
|
||||||
|
|
|
@ -54,7 +54,8 @@ export interface DirectiveDecorator {
|
||||||
*
|
*
|
||||||
* **Metadata Properties:**
|
* **Metadata Properties:**
|
||||||
*
|
*
|
||||||
* * **exportAs** - name under which the component instance is exported in a template
|
* * **exportAs** - name under which the component instance is exported in a template. Can be
|
||||||
|
* given a single name or a comma-delimited list of names.
|
||||||
* * **host** - map of class property to host element bindings for events, properties and
|
* * **host** - map of class property to host element bindings for events, properties and
|
||||||
* attributes
|
* attributes
|
||||||
* * **inputs** - list of class property names to data-bind as component inputs
|
* * **inputs** - list of class property names to data-bind as component inputs
|
||||||
|
|
|
@ -469,6 +469,20 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||||
.toBeAnInstanceOf(ExportDir);
|
.toBeAnInstanceOf(ExportDir);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 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',
|
it('should make the assigned component accessible in property bindings, even if they were declared before the component',
|
||||||
() => {
|
() => {
|
||||||
TestBed.configureTestingModule({declarations: [MyComp, ChildComp]});
|
TestBed.configureTestingModule({declarations: [MyComp, ChildComp]});
|
||||||
|
@ -2442,6 +2456,10 @@ class SomeImperativeViewport {
|
||||||
class ExportDir {
|
class ExportDir {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[multiple-export-as]', exportAs: 'dirX, dirY'})
|
||||||
|
export class DirectiveWithMultipleExportAsNames {
|
||||||
|
}
|
||||||
|
|
||||||
@Component({selector: 'comp'})
|
@Component({selector: 'comp'})
|
||||||
class ComponentWithoutView {
|
class ComponentWithoutView {
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue