From f1269d98dc707cac27dddddbfd616072702478b3 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Thu, 17 Oct 2019 16:02:21 -0700 Subject: [PATCH] feat(ivy): input type coercion for template type-checking (#33243) Often the types of an `@Input`'s field don't fully reflect the types of assignable values. This can happen when an input has a getter/setter pair where the getter always returns a narrow type, and the setter coerces a wider value down to the narrow type. For example, you could imagine an input of the form: ```typescript @Input() get value(): string { return this._value; } set value(v: {toString(): string}) { this._value = v.toString(); } ``` Here, the getter always returns a `string`, but the setter accepts any value that can be `toString()`'d, and coerces it to a string. Unfortunately TypeScript does not actually support this syntax, and so Angular users are forced to type their setters as narrowly as the getters, even though at runtime the coercion works just fine. To support these kinds of patterns (e.g. as used by Material), this commit adds a compiler feature called "input coercion". When a binding is made to the 'value' input of a directive like MatInput, the compiler will look for a static field with the name ngAcceptInputType_value. If such a field is found the type-checking expression for the input will use the static field's type instead of the type for the @Input field,allowing for the expression of a type conversion between the binding expression and the value being written to the input's field. To solve the case above, for example, MatInput might write: ```typescript class MatInput { // rest of the directive... static ngAcceptInputType_value: {toString(): string}; } ``` FW-1475 #resolve PR Close #33243 --- aio/content/guide/aot-compiler.md | 74 +++++++++++++++++++ .../src/ngtsc/metadata/src/api.ts | 1 + .../src/ngtsc/metadata/src/util.ts | 24 +++++- .../src/ngtsc/scope/test/local_spec.ts | 1 + .../src/ngtsc/typecheck/src/api.ts | 6 ++ .../src/ngtsc/typecheck/src/context.ts | 1 + .../src/ngtsc/typecheck/src/environment.ts | 3 +- .../ngtsc/typecheck/src/type_constructor.ts | 42 ++++++++--- .../src/ngtsc/typecheck/test/test_utils.ts | 11 ++- .../typecheck/test/type_constructor_spec.ts | 43 +++++++++++ .../test/ngtsc/template_typecheck_spec.ts | 70 ++++++++++++++++++ 11 files changed, 261 insertions(+), 15 deletions(-) diff --git a/aio/content/guide/aot-compiler.md b/aio/content/guide/aot-compiler.md index 35b6dec4f2..91977414c1 100644 --- a/aio/content/guide/aot-compiler.md +++ b/aio/content/guide/aot-compiler.md @@ -677,6 +677,80 @@ In this example it is recommended to include the checking of `address` in the `* } ``` +### Input setter coercion + +Occasionally it is desirable for the `@Input` of a directive or component to alter the value bound to it, typically using a getter/setter pair for the input. As an example, consider this custom button component: + +Consider the following directive: + +```typescript +@Component({ + selector: 'submit-button', + template: ` +
+ ' +
+ `, +}) +class SubmitButton { + private _disabled: boolean; + + get disabled(): boolean { + return this._disabled; + } + + set disabled(value: boolean) { + this._disabled = value; + } +} +``` + +Here, the `disabled` input of the component is being passed on to the `