fix(compiler): generate inputs with aliases properly (#26774)
PR Close #26774
This commit is contained in:
parent
c048358cf9
commit
19fcfc3d00
|
@ -110,12 +110,14 @@ export function extractDirectiveMetadata(
|
|||
// fields.
|
||||
const inputsFromMeta = parseFieldToPropertyMapping(directive, 'inputs', reflector, checker);
|
||||
const inputsFromFields = parseDecoratedFields(
|
||||
filterToMembersWithDecorator(decoratedElements, 'Input', coreModule), reflector, checker);
|
||||
filterToMembersWithDecorator(decoratedElements, 'Input', coreModule), reflector, checker,
|
||||
resolveInput);
|
||||
|
||||
// And outputs.
|
||||
const outputsFromMeta = parseFieldToPropertyMapping(directive, 'outputs', reflector, checker);
|
||||
const outputsFromFields = parseDecoratedFields(
|
||||
filterToMembersWithDecorator(decoratedElements, 'Output', coreModule), reflector, checker);
|
||||
filterToMembersWithDecorator(decoratedElements, 'Output', coreModule), reflector, checker,
|
||||
resolveOutput) as{[field: string]: string};
|
||||
// Construct the list of queries.
|
||||
const contentChildFromFields = queriesFromFields(
|
||||
filterToMembersWithDecorator(decoratedElements, 'ContentChild', coreModule), reflector,
|
||||
|
@ -330,7 +332,8 @@ function parseFieldToPropertyMapping(
|
|||
*/
|
||||
function parseDecoratedFields(
|
||||
fields: {member: ClassMember, decorators: Decorator[]}[], reflector: ReflectionHost,
|
||||
checker: ts.TypeChecker): {[field: string]: string} {
|
||||
checker: ts.TypeChecker, mapValueResolver: (publicName: string, internalName: string) =>
|
||||
string | string[]): {[field: string]: string | string[]} {
|
||||
return fields.reduce(
|
||||
(results, field) => {
|
||||
const fieldName = field.member.name;
|
||||
|
@ -344,7 +347,7 @@ function parseDecoratedFields(
|
|||
if (typeof property !== 'string') {
|
||||
throw new Error(`Decorator argument must resolve to a string`);
|
||||
}
|
||||
results[fieldName] = property;
|
||||
results[fieldName] = mapValueResolver(property, fieldName);
|
||||
} else {
|
||||
// Too many arguments.
|
||||
throw new Error(
|
||||
|
@ -353,7 +356,15 @@ function parseDecoratedFields(
|
|||
});
|
||||
return results;
|
||||
},
|
||||
{} as{[field: string]: string});
|
||||
{} as{[field: string]: string | string[]});
|
||||
}
|
||||
|
||||
function resolveInput(publicName: string, internalName: string) {
|
||||
return [publicName, internalName];
|
||||
}
|
||||
|
||||
function resolveOutput(publicName: string, internalName: string) {
|
||||
return publicName;
|
||||
}
|
||||
|
||||
export function queriesFromFields(
|
||||
|
|
|
@ -518,7 +518,10 @@ function tcbGetInputBindingExpressions(
|
|||
// is desired. Invert `dir.inputs` into `propMatch` to create this map.
|
||||
const propMatch = new Map<string, string>();
|
||||
const inputs = dir.inputs;
|
||||
Object.keys(inputs).forEach(key => propMatch.set(inputs[key], key));
|
||||
Object.keys(inputs).forEach(key => {
|
||||
Array.isArray(inputs[key]) ? propMatch.set(inputs[key][0], key) :
|
||||
propMatch.set(inputs[key] as string, key);
|
||||
});
|
||||
|
||||
// Add a binding expression to the map for each input of the directive that has a
|
||||
// matching binding.
|
||||
|
|
|
@ -1887,7 +1887,7 @@ describe('compiler compliance', () => {
|
|||
type: LifecycleComp,
|
||||
selectors: [["lifecycle-comp"]],
|
||||
factory: function LifecycleComp_Factory(t) { return new (t || LifecycleComp)(); },
|
||||
inputs: {nameMin: "name"},
|
||||
inputs: {nameMin: ["name", "nameMin"]},
|
||||
features: [$r3$.ɵNgOnChangesFeature],
|
||||
consts: 0,
|
||||
vars: 0,
|
||||
|
@ -2301,7 +2301,7 @@ describe('compiler compliance', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('inherited bare classes', () => {
|
||||
describe('inherited base classes', () => {
|
||||
it('should add ngBaseDef if one or more @Input is present', () => {
|
||||
const files = {
|
||||
app: {
|
||||
|
|
|
@ -56,7 +56,7 @@ describe('compiler compliance: listen()', () => {
|
|||
…
|
||||
inputs:{
|
||||
componentInput: "componentInput",
|
||||
originalComponentInput: "renamedComponentInput"
|
||||
originalComponentInput: ["renamedComponentInput", "originalComponentInput"]
|
||||
},
|
||||
outputs: {
|
||||
componentOutput: "componentOutput",
|
||||
|
@ -70,7 +70,7 @@ describe('compiler compliance: listen()', () => {
|
|||
…
|
||||
inputs:{
|
||||
directiveInput: "directiveInput",
|
||||
originalDirectiveInput: "renamedDirectiveInput"
|
||||
originalDirectiveInput: ["renamedDirectiveInput", "originalDirectiveInput"]
|
||||
},
|
||||
outputs: {
|
||||
directiveOutput: "directiveOutput",
|
||||
|
|
|
@ -86,7 +86,7 @@ export interface R3DirectiveMetadata {
|
|||
/**
|
||||
* A mapping of input field names to the property names.
|
||||
*/
|
||||
inputs: {[field: string]: string};
|
||||
inputs: {[field: string]: string | string[]};
|
||||
|
||||
/**
|
||||
* A mapping of output field names to the property names.
|
||||
|
|
|
@ -532,12 +532,15 @@ function stringAsType(str: string): o.Type {
|
|||
return o.expressionType(o.literal(str));
|
||||
}
|
||||
|
||||
function stringMapAsType(map: {[key: string]: string}): o.Type {
|
||||
const mapValues = Object.keys(map).map(key => ({
|
||||
function stringMapAsType(map: {[key: string]: string | string[]}): o.Type {
|
||||
const mapValues = Object.keys(map).map(key => {
|
||||
const value = Array.isArray(map[key]) ? map[key][0] : map[key];
|
||||
return {
|
||||
key,
|
||||
value: o.literal(map[key]),
|
||||
value: o.literal(value),
|
||||
quoted: true,
|
||||
}));
|
||||
};
|
||||
});
|
||||
return o.expressionType(o.literalMap(mapValues));
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ export interface DirectiveMeta {
|
|||
*
|
||||
* Goes from property names to field names.
|
||||
*/
|
||||
inputs: {[property: string]: string};
|
||||
inputs: {[property: string]: string | string[]};
|
||||
|
||||
/**
|
||||
* Set of outputs which this directive claims.
|
||||
|
|
|
@ -67,8 +67,8 @@ export function asLiteral(value: any): o.Expression {
|
|||
return o.literal(value, o.INFERRED_TYPE);
|
||||
}
|
||||
|
||||
export function conditionallyCreateMapObjectLiteral(keys: {[key: string]: string}): o.Expression|
|
||||
null {
|
||||
export function conditionallyCreateMapObjectLiteral(keys: {[key: string]: string | string[]}):
|
||||
o.Expression|null {
|
||||
if (Object.getOwnPropertyNames(keys).length > 0) {
|
||||
return mapToExpression(keys);
|
||||
}
|
||||
|
|
|
@ -153,13 +153,14 @@ function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMet
|
|||
const inputsFromMetadata = parseInputOutputs(metadata.inputs || []);
|
||||
const outputsFromMetadata = parseInputOutputs(metadata.outputs || []);
|
||||
|
||||
const inputsFromType: StringMap = {};
|
||||
const inputsFromType: {[key: string]: string | string[]} = {};
|
||||
const outputsFromType: StringMap = {};
|
||||
for (const field in propMetadata) {
|
||||
if (propMetadata.hasOwnProperty(field)) {
|
||||
propMetadata[field].forEach(ann => {
|
||||
if (isInput(ann)) {
|
||||
inputsFromType[field] = ann.bindingPropertyName || field;
|
||||
inputsFromType[field] =
|
||||
ann.bindingPropertyName ? [ann.bindingPropertyName, field] : field;
|
||||
} else if (isOutput(ann)) {
|
||||
outputsFromType[field] = ann.bindingPropertyName || field;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import {InjectorDef, defineInjectable} from '@angular/core/src/di/defs';
|
|||
import {Injectable} from '@angular/core/src/di/injectable';
|
||||
import {inject, setCurrentInjector} from '@angular/core/src/di/injector_compatibility';
|
||||
import {ivyEnabled} from '@angular/core/src/ivy_switch';
|
||||
import {Component, HostBinding, HostListener, Input, Output, Pipe} from '@angular/core/src/metadata/directives';
|
||||
import {Component, Directive, HostBinding, HostListener, Input, Output, Pipe} from '@angular/core/src/metadata/directives';
|
||||
import {NgModule, NgModuleDef} from '@angular/core/src/metadata/ng_module';
|
||||
import {ComponentDef, PipeDef} from '@angular/core/src/render3/interfaces/definition';
|
||||
|
||||
|
@ -252,6 +252,33 @@ ivyEnabled && describe('render3 jit', () => {
|
|||
expect(pipeDef.pure).toBe(true, 'pipe should be pure');
|
||||
});
|
||||
|
||||
it('should add @Input properties to a component', () => {
|
||||
@Component({
|
||||
selector: 'input-comp',
|
||||
template: 'test',
|
||||
})
|
||||
class InputComp {
|
||||
@Input('publicName') privateName = 'name1';
|
||||
}
|
||||
|
||||
const InputCompAny = InputComp as any;
|
||||
expect(InputCompAny.ngComponentDef.inputs).toEqual({publicName: 'privateName'});
|
||||
expect(InputCompAny.ngComponentDef.declaredInputs).toEqual({privateName: 'privateName'});
|
||||
});
|
||||
|
||||
it('should add @Input properties to a directive', () => {
|
||||
@Directive({
|
||||
selector: '[dir]',
|
||||
})
|
||||
class InputDir {
|
||||
@Input('publicName') privateName = 'name1';
|
||||
}
|
||||
|
||||
const InputDirAny = InputDir as any;
|
||||
expect(InputDirAny.ngDirectiveDef.inputs).toEqual({publicName: 'privateName'});
|
||||
expect(InputDirAny.ngDirectiveDef.declaredInputs).toEqual({privateName: 'privateName'});
|
||||
});
|
||||
|
||||
it('should add ngBaseDef to types with @Input properties', () => {
|
||||
class C {
|
||||
@Input('alias1')
|
||||
|
|
Loading…
Reference in New Issue