New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(core): add ability to transform input values #50420
Conversation
6130000
to
a91cd4a
Compare
}; | ||
} else { | ||
result[key] = { | ||
bindingPropertyName: value[0], | ||
classPropertyName: value[1], | ||
transformFunction: value[2] || null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't you keep these properties sorted because of *morphism? (or at least in the same order as other usages)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if that makes a difference. My understanding is that only the shape of the object matters (e.g. it has the same set of properties).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually the order in which you add the properties is relevant and that also applies to object literals
According to the HTML specification most attributes are defined as strings, however some can be interpreted as different types like booleans or numbers. [In the HTML standard](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes), boolean attributes are considered `true` if they are present on a DOM node and `false` if they are omitted. Common examples of boolean attributes are `disabled` on interactive elements like `<button>` or `checked` on `<input type="checkbox">`. Another example of an attribute that is defined as a string, but interpreted as a different type is the `value` attribute of `<input type="number">` which logs a warning and ignores the value if it can't be parsed as a number. Historically, authoring Angular inputs that match the native behavior in a type-safe way has been difficult for developers, because Angular interprets all static attributes as strings. While some recent TypeScript versions made this easier by allowing setters and getters to have different types, supporting this pattern still requires a lot of boilerplate and additional properties to be declared. For example, currently developers have to write something like this to have a `disabled` input that behaves like the native one: ```typescript import {Directive, Input} from '@angular/core'; @directive({selector: 'mat-checkbox'}) export class MatCheckbox { @input() get disabled() { return this._disabled; } set disabled(value: any) { this._disabled = typeof value === 'boolean' ? value : (value != null && value !== 'false'); } private _disabled = false; } ``` This feature aims to address the issue by introducing a `transform` property on inputs. If an input has a `transform` function, any values set through the template will be passed through the function before being assigned to the directive instance. The example from above can be rewritten to the following: ```typescript import {Directive, Input, booleanAttribute} from '@angular/core'; @directive({selector: 'mat-checkbox'}) export class MatCheckbox { @input({transform: booleanAttribute}) disabled: boolean = false; } ``` These changes also add the `booleanAttribute` and `numberAttribute` utilities to `@angular/core` since they're common enough to be useful for most projects. Fixes angular#8968. Fixes angular#14761.
a91cd4a
to
d4641ad
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed-for: public-api
Since there is no documentation yet, here are a few things I observed, which might be good to know for others interested in this change:
|
There's no way to get the
This is incorrect, the directive will be marked as dirty, however |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reviewed-for: fw-core, fw-forms, public-api
That's not correct. The following solution works with // @experimentalDecorators: true
declare function Input<This extends object, In, K extends keyof This>(
transform: (this: This, v: In) => This[K] // provide 'this' in contextual type, ensure return type matches
): (target: This, key: K) => void;
I see. I mixed up |
@ajafff that's an interesting approach, but it doesn't appear to work with arrow functions. Either way, I'll keep the type annotations as they are and we an iterate over it once the feature is merged. |
This PR was merged into the repository by commit 68017d4. |
Excited to test this ❤️ |
Great! |
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@angular/animations](https://github.com/angular/angular) | dependencies | minor | [`16.0.4` -> `16.1.1`](https://renovatebot.com/diffs/npm/@angular%2fanimations/16.0.4/16.1.1) | | [@angular/common](https://github.com/angular/angular) | dependencies | minor | [`16.0.4` -> `16.1.1`](https://renovatebot.com/diffs/npm/@angular%2fcommon/16.0.4/16.1.1) | | [@angular/compiler](https://github.com/angular/angular) | dependencies | minor | [`16.0.4` -> `16.1.1`](https://renovatebot.com/diffs/npm/@angular%2fcompiler/16.0.4/16.1.1) | | [@angular/compiler-cli](https://github.com/angular/angular/tree/main/packages/compiler-cli) ([source](https://github.com/angular/angular)) | devDependencies | minor | [`16.0.4` -> `16.1.1`](https://renovatebot.com/diffs/npm/@angular%2fcompiler-cli/16.0.4/16.1.1) | | [@angular/core](https://github.com/angular/angular) | dependencies | minor | [`16.0.4` -> `16.1.1`](https://renovatebot.com/diffs/npm/@angular%2fcore/16.0.4/16.1.1) | | [@angular/forms](https://github.com/angular/angular) | dependencies | minor | [`16.0.4` -> `16.1.1`](https://renovatebot.com/diffs/npm/@angular%2fforms/16.0.4/16.1.1) | | [@angular/platform-browser](https://github.com/angular/angular) | dependencies | minor | [`16.0.4` -> `16.1.1`](https://renovatebot.com/diffs/npm/@angular%2fplatform-browser/16.0.4/16.1.1) | | [@angular/platform-browser-dynamic](https://github.com/angular/angular) | dependencies | minor | [`16.0.4` -> `16.1.1`](https://renovatebot.com/diffs/npm/@angular%2fplatform-browser-dynamic/16.0.4/16.1.1) | | [zone.js](https://github.com/angular/angular) ([changelog](https://github.com/angular/angular/blob/master/packages/zone.js/CHANGELOG.md)) | dependencies | patch | [`0.13.0` -> `0.13.1`](https://renovatebot.com/diffs/npm/zone.js/0.13.0/0.13.1) | --- ### Release Notes <details> <summary>angular/angular (@​angular/animations)</summary> ### [`v16.1.1`](https://github.com/angular/angular/blob/HEAD/CHANGELOG.md#​1611-2023-06-14) [Compare Source](angular/angular@16.1.0...16.1.1) ##### compiler-cli | Commit | Type | Description | | -- | -- | -- | | [71360b3a3e](angular/angular@71360b3) | fix | libraries compiled with v16.1+ breaking with Angular framework v16.0.x ([#​50715](angular/angular#50715)) | ##### core | Commit | Type | Description | | -- | -- | -- | | [d9bed48eb5](angular/angular@d9bed48) | fix | extend toSignal to accept any Subscribable ([#​50162](angular/angular#50162)) | ##### migrations | Commit | Type | Description | | -- | -- | -- | | [5e1d8444ae](angular/angular@5e1d844) | fix | Prevent a component from importing itself. ([#​50554](angular/angular#50554)) | <!-- CHANGELOG SPLIT MARKER --> ### [`v16.1.0`](https://github.com/angular/angular/blob/HEAD/CHANGELOG.md#​1610-2023-06-13) [Compare Source](angular/angular@16.0.6...16.1.0) ##### compiler | Commit | Type | Description | | -- | -- | -- | | [4e663297c5](angular/angular@4e66329) | fix | error when reading compiled input transforms metadata in JIT mode ([#​50600](angular/angular#50600)) | | [721bc72649](angular/angular@721bc72) | fix | resolve deprecation warning with TypeScript 5.1 ([#​50460](angular/angular#50460)) | ##### core | Commit | Type | Description | | -- | -- | -- | | [68017d4e75](angular/angular@68017d4) | feat | add ability to transform input values ([#​50420](angular/angular#50420)) | | [69dadd2502](angular/angular@69dadd2) | feat | support TypeScript 5.1 ([#​50156](angular/angular#50156)) | | [c0ebe34cbd](angular/angular@c0ebe34) | fix | add additional component metadata to component ID generation ([#​50336](angular/angular#50336)) | ##### http | Commit | Type | Description | | -- | -- | -- | | [85c5427582](angular/angular@85c5427) | feat | Introduction of the `fetch` Backend for the `HttpClient` ([#​50247](angular/angular#50247)) | <!-- CHANGELOG SPLIT MARKER --> ### [`v16.0.6`](https://github.com/angular/angular/blob/HEAD/CHANGELOG.md#​1606-2023-06-13) [Compare Source](angular/angular@16.0.5...16.0.6) ##### core | Commit | Type | Description | | -- | -- | -- | | [05ac0868c9](angular/angular@05ac086) | fix | avoid duplicated content during hydration while processing a component with i18n ([#​50644](angular/angular#50644)) | <!-- CHANGELOG SPLIT MARKER --> ### [`v16.0.5`](https://github.com/angular/angular/blob/HEAD/CHANGELOG.md#​1605-2023-06-08) [Compare Source](angular/angular@16.0.4...16.0.5) ##### compiler | Commit | Type | Description | | -- | -- | -- | | [703b8fcac1](angular/angular@703b8fc) | fix | do not remove comments in component styles ([#​50346](angular/angular#50346)) | ##### core | Commit | Type | Description | | -- | -- | -- | | [2b6da93e19](angular/angular@2b6da93) | fix | incorrectly throwing error for self-referencing component ([#​50559](angular/angular#50559)) | | [c992109d6c](angular/angular@c992109) | fix | wait for HTTP in `ngOnInit` correctly before server render ([#​50573](angular/angular#50573)) | ##### platform-server | Commit | Type | Description | | -- | -- | -- | | [c0d4086c6e](angular/angular@c0d4086) | fix | surface errors during rendering ([#​50587](angular/angular#50587)) | <!-- CHANGELOG SPLIT MARKER --> </details> <details> <summary>angular/angular (zone.js)</summary> ### [`v0.13.1`](https://github.com/angular/angular/blob/HEAD/packages/zone.js/CHANGELOG.md#v0131-httpsgithubcomangularangularcomparezonejs-0130zonejs-v0131-2023-06-09) [Compare Source](angular/angular@zone.js-0.13.0...zone.js-0.13.1) ##### Bug Fixes - **zone.js:** enable monkey patching of the `queueMicrotask()` API in node.js ([#​50467](angular/angular#50467)) ([381cb98](angular/angular@381cb98)) - **zone.js:** enable monkey patching of the `queueMicrotask()` API in node.js ([#​50530](angular/angular#50530)) ([7837f71](angular/angular@7837f71)) - **zone.js:** patch entire promise in node ([#​50552](angular/angular#50552)) ([cb31dbc](angular/angular@cb31dbc)), closes [#​50513](angular/angular#50513) [#​50457](angular/angular#50457) [#​50414](angular/angular#50414) [#​49930](angular/angular#49930) - **zone.js:** revert Mocha it.skip, describe.skip method patch ([#​49329](angular/angular#49329)) ([5a2b622](angular/angular@5a2b622)) ##### Features - **zone.js:** jest 29 should ignore uncaught error console log ([#​49325](angular/angular#49325)) ([bc412fd](angular/angular@bc412fd)), closes [#​49110](angular/angular#49110) ##### Reverts - Revert "fix(zone.js): enable monkey patching of the `queueMicrotask()` API in node.js ([#​50467](angular/angular#50467))" ([#​50529](angular/angular#50529)) ([7567348](angular/angular@7567348)), closes [#​50467](angular/angular#50467) [#​50529](angular/angular#50529) [#​50529](angular/angular#50529) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS4xMTUuMiIsInVwZGF0ZWRJblZlciI6IjM1LjExNy4yIiwidGFyZ2V0QnJhbmNoIjoiZGV2ZWxvcCJ9--> Co-authored-by: cabr2-bot <cabr2.help@gmail.com> Reviewed-on: https://codeberg.org/Calciumdibromid/CaBr2/pulls/1927 Reviewed-by: Epsilon_02 <epsilon_02@noreply.codeberg.org> Co-authored-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org> Co-committed-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org>
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
According to the HTML specification most attributes are defined as strings, however some can be interpreted as different types like booleans or numbers. [In the HTML standard](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes), boolean attributes are considered `true` if they are present on a DOM node and `false` if they are omitted. Common examples of boolean attributes are `disabled` on interactive elements like `<button>` or `checked` on `<input type="checkbox">`. Another example of an attribute that is defined as a string, but interpreted as a different type is the `value` attribute of `<input type="number">` which logs a warning and ignores the value if it can't be parsed as a number. Historically, authoring Angular inputs that match the native behavior in a type-safe way has been difficult for developers, because Angular interprets all static attributes as strings. While some recent TypeScript versions made this easier by allowing setters and getters to have different types, supporting this pattern still requires a lot of boilerplate and additional properties to be declared. For example, currently developers have to write something like this to have a `disabled` input that behaves like the native one: ```typescript import {Directive, Input} from '@angular/core'; @directive({selector: 'mat-checkbox'}) export class MatCheckbox { @input() get disabled() { return this._disabled; } set disabled(value: any) { this._disabled = typeof value === 'boolean' ? value : (value != null && value !== 'false'); } private _disabled = false; } ``` This feature aims to address the issue by introducing a `transform` property on inputs. If an input has a `transform` function, any values set through the template will be passed through the function before being assigned to the directive instance. The example from above can be rewritten to the following: ```typescript import {Directive, Input, booleanAttribute} from '@angular/core'; @directive({selector: 'mat-checkbox'}) export class MatCheckbox { @input({transform: booleanAttribute}) disabled: boolean = false; } ``` These changes also add the `booleanAttribute` and `numberAttribute` utilities to `@angular/core` since they're common enough to be useful for most projects. Fixes angular#8968. Fixes angular#14761. PR Close angular#50420
According to the HTML specification most attributes are defined as strings, however some can be interpreted as different types like booleans or numbers. [In the HTML standard](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes), boolean attributes are considered `true` if they are present on a DOM node and `false` if they are omitted. Common examples of boolean attributes are `disabled` on interactive elements like `<button>` or `checked` on `<input type="checkbox">`. Another example of an attribute that is defined as a string, but interpreted as a different type is the `value` attribute of `<input type="number">` which logs a warning and ignores the value if it can't be parsed as a number. Historically, authoring Angular inputs that match the native behavior in a type-safe way has been difficult for developers, because Angular interprets all static attributes as strings. While some recent TypeScript versions made this easier by allowing setters and getters to have different types, supporting this pattern still requires a lot of boilerplate and additional properties to be declared. For example, currently developers have to write something like this to have a `disabled` input that behaves like the native one: ```typescript import {Directive, Input} from '@angular/core'; @directive({selector: 'mat-checkbox'}) export class MatCheckbox { @input() get disabled() { return this._disabled; } set disabled(value: any) { this._disabled = typeof value === 'boolean' ? value : (value != null && value !== 'false'); } private _disabled = false; } ``` This feature aims to address the issue by introducing a `transform` property on inputs. If an input has a `transform` function, any values set through the template will be passed through the function before being assigned to the directive instance. The example from above can be rewritten to the following: ```typescript import {Directive, Input, booleanAttribute} from '@angular/core'; @directive({selector: 'mat-checkbox'}) export class MatCheckbox { @input({transform: booleanAttribute}) disabled: boolean = false; } ``` These changes also add the `booleanAttribute` and `numberAttribute` utilities to `@angular/core` since they're common enough to be useful for most projects. Fixes angular#8968. Fixes angular#14761. PR Close angular#50420
According to the HTML specification most attributes are defined as strings, however some can be interpreted as different types like booleans or numbers. In the HTML standard, boolean attributes are considered
true
if they are present on a DOM node andfalse
if they are omitted. Common examples of boolean attributes aredisabled
on interactive elements like<button>
orchecked
on<input type="checkbox">
. Another example of an attribute that is defined as a string, but interpreted as a different type is thevalue
attribute of<input type="number">
which logs a warning and ignores the value if it can't be parsed as a number.Historically, authoring Angular inputs that match the native behavior in a type-safe way has been difficult for developers, because Angular interprets all static attributes as strings. While some recent TypeScript versions made this easier by allowing setters and getters to have different types, supporting this pattern still requires a lot of boilerplate and additional properties to be declared. For example, currently developers have to write something like this to have a
disabled
input that behaves like the native one:This feature aims to address the issue by introducing a
transform
property on inputs. If an input has atransform
function, any values set through the template will be passed through the function before being assigned to the directive instance. The example from above can be rewritten to the following:These changes also add the
booleanAttribute
andnumberAttribute
utilities to@angular/core
since they're common enough to be useful for most projects.Fixes #8968.
Fixes #14761.