fix(ivy): type-checking of properties which map to multiple fields (#34649)

It's possible to declare multiple inputs for a directive/component which all
map to the same property name. This is usually done in error, as only one of
any bindings to the property will "win".

In the template type-checker, an error was previously being raised as a
result of this ambiguity. Specifically, a type constructor was produced
which required a binding for each field, but only one of the fields had
a value via the binding. TypeScript would (rightfully) error on missing
values for the remaining fields. This ultimately was happening when the
code which generated the default values for "unset" inputs belonging to
directives or pipes used the final mapping from properties to fields as
a source for field names.

Instead, this commit uses the original list of fields to generate unset
input values, which correctly provides values for fields which shared a
property name but didn't receive the final binding.

PR Close #34649
This commit is contained in:
Alex Rickabaugh 2019-12-18 17:21:16 -08:00 committed by Andrew Kushnir
parent 6b468f9b2e
commit 22c957a93d
2 changed files with 31 additions and 1 deletions

View File

@ -1187,7 +1187,10 @@ function tcbGetDirectiveInputs(
// To determine which of directive's inputs are unset, we keep track of the set of field names
// that have not been seen yet. A field is removed from this set once a binding to it is found.
const unsetFields = new Set(propMatch.values());
// Note: it's actually important here that `propMatch.values()` isn't used, as there can be
// multiple fields which share the same property name and only one of them will be listed as a
// value in `propMatch`.
const unsetFields = new Set(Object.keys(inputs));
el.inputs.forEach(processAttribute);
el.attributes.forEach(processAttribute);

View File

@ -174,6 +174,33 @@ export declare class AnimationEvent {
.toEqual(`Argument of type 'string' is not assignable to parameter of type 'number'.`);
});
it('should support one input property mapping to multiple fields', () => {
env.write('test.ts', `
import {Component, Directive, Input, NgModule} from '@angular/core';
@Directive({
selector: '[dir]',
})
export class Dir {
@Input('propertyName') fieldA!: string;
@Input('propertyName') fieldB!: string;
}
@Component({
selector: 'test-cmp',
template: '<div dir propertyName="test"></div>',
})
export class Cmp {}
@NgModule({declarations: [Dir, Cmp]})
export class Module {}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(0);
});
it('should check event bindings', () => {
env.tsconfig({fullTemplateTypeCheck: true, strictOutputEventTypes: true});
env.write('test.ts', `