Skip to content
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

fix(forms): Make radio buttons respect [attr.disabled] #48864

Closed
wants to merge 1 commit into from

Conversation

dylhunn
Copy link
Contributor

@dylhunn dylhunn commented Jan 27, 2023

setDisabledState is supposed to be called whenever the disabled state of a control changes, including upon control creation. However, a longstanding bug caused the method to not fire when an enabled control was attached. This bug was fixed in v15.

This had a side effect: previously, it was possible to instantiate a reactive form control with [attr.disabled]=true, even though the the corresponding control was enabled in the model. (Note that the similar-looking property binding version [disabled]=true was always rejected, though.) This resulted in a mismatch between the model and the DOM. Now, because setDisabledState is always called, the value in the DOM will be immediately overwritten with the "correct" enabled value.

Users should instead disable the control directly in their model. (There are many ways to do this, such as using the {value: 'foo', disabled: true} constructor format, or immediately calling FooControl.disable() in ngOnInit.)

If this incompatibility is too breaking, you may also opt out using FormsModule.withConfig or ReactiveFormsModule.withConfig at the time you import it, via the callSetDisabledState option.

However, there is an exceptional case: radio buttons. Because Reactive Forms models the entire group of radio buttons as a single FormControl, there is no way to control the disabled state for individual radios, so they can no longer be configured as disabled.

In this PR, we have special cased radio buttons to ignore their first call to setDisabledState when in callSetDisabledState: 'always' mode. This preserves the old behavior.

Related to #48350.

@dylhunn dylhunn added action: review The PR is still awaiting reviews from at least one requested reviewer area: forms target: patch This PR is targeted for the next patch release forms: disabling controls forms: ControlValueAccessor labels Jan 27, 2023
@ngbot ngbot bot added this to the Backlog milestone Jan 27, 2023
@dylhunn dylhunn removed the action: review The PR is still awaiting reviews from at least one requested reviewer label Jan 27, 2023
@dylhunn dylhunn removed the request for review from AndrewKushnir January 27, 2023 01:45
@dylhunn dylhunn marked this pull request as draft January 27, 2023 01:46
@dylhunn dylhunn marked this pull request as ready for review January 27, 2023 01:52
@dylhunn dylhunn added the action: review The PR is still awaiting reviews from at least one requested reviewer label Jan 27, 2023
@dylhunn dylhunn removed the action: review The PR is still awaiting reviews from at least one requested reviewer label Jan 27, 2023
@dylhunn
Copy link
Contributor Author

dylhunn commented Feb 2, 2023

Thanks @AndrewKushnir and sorry for the delay. Back to you on this.

@dylhunn dylhunn added the action: review The PR is still awaiting reviews from at least one requested reviewer label Feb 2, 2023
Copy link
Contributor

@AndrewKushnir AndrewKushnir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (just a couple minor comments) 👍

@AndrewKushnir AndrewKushnir removed the action: review The PR is still awaiting reviews from at least one requested reviewer label Feb 2, 2023
`setDisabledState` is supposed to be called whenever the disabled state of a control changes, including upon control creation. However, a longstanding bug caused the method to not fire when an *enabled* control was attached. This bug was fixed in v15.

This had a side effect: previously, it was possible to instantiate a reactive form control with `[attr.disabled]=true`, even though the the corresponding control was enabled in the model. (Note that the similar-looking property binding version `[disabled]=true` was always rejected, though.) This resulted in a mismatch between the model and the DOM. Now, because `setDisabledState` is always called, the value in the DOM will be immediately overwritten with the "correct" enabled value.

Users should instead disable the control directly in their model. (There are many ways to do this, such as using the `{value: 'foo', disabled: true}` constructor format, or immediately calling `FooControl.disable()` in `ngOnInit`.)

If this incompatibility is too breaking, you may also opt out using `FormsModule.withConfig` or `ReactiveFormsModule.withConfig` at the time you import it, via the `callSetDisabledState` option.

However, there is an exceptional case: radio buttons. Because Reactive Forms models the entire group of radio buttons as a single `FormControl`, there is no way to control the disabled state for individual radios, so they can no longer be configured as disabled.

In this PR, we have special cased radio buttons to ignore their first call to `setDisabledState` when in `callSetDisabledState: 'always'` mode. This preserves the old behavior.
@dylhunn
Copy link
Contributor Author

dylhunn commented Feb 3, 2023

merge-assistance: the Public API change is to prevent a spurious documentation symbol for RadioControlValueAccessor.setDisabledState. It should be fine to merge with just Andrew's approval.

@dylhunn dylhunn added the action: merge The PR is ready for merge by the caretaker label Feb 3, 2023
@dylhunn dylhunn added the merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note label Feb 3, 2023
Copy link
Contributor

@jessicajaniuk jessicajaniuk left a 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

@pkozlowski-opensource
Copy link
Member

This PR was merged into the repository by commit 5968561.

pkozlowski-opensource pushed a commit that referenced this pull request Feb 10, 2023
`setDisabledState` is supposed to be called whenever the disabled state of a control changes, including upon control creation. However, a longstanding bug caused the method to not fire when an *enabled* control was attached. This bug was fixed in v15.

This had a side effect: previously, it was possible to instantiate a reactive form control with `[attr.disabled]=true`, even though the the corresponding control was enabled in the model. (Note that the similar-looking property binding version `[disabled]=true` was always rejected, though.) This resulted in a mismatch between the model and the DOM. Now, because `setDisabledState` is always called, the value in the DOM will be immediately overwritten with the "correct" enabled value.

Users should instead disable the control directly in their model. (There are many ways to do this, such as using the `{value: 'foo', disabled: true}` constructor format, or immediately calling `FooControl.disable()` in `ngOnInit`.)

If this incompatibility is too breaking, you may also opt out using `FormsModule.withConfig` or `ReactiveFormsModule.withConfig` at the time you import it, via the `callSetDisabledState` option.

However, there is an exceptional case: radio buttons. Because Reactive Forms models the entire group of radio buttons as a single `FormControl`, there is no way to control the disabled state for individual radios, so they can no longer be configured as disabled.

In this PR, we have special cased radio buttons to ignore their first call to `setDisabledState` when in `callSetDisabledState: 'always'` mode. This preserves the old behavior.

PR Close #48864
crapStone pushed a commit to Calciumdibromid/CaBr2 that referenced this pull request Feb 15, 2023
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@angular/animations](https://github.com/angular/angular) | dependencies | patch | [`15.1.4` -> `15.1.5`](https://renovatebot.com/diffs/npm/@angular%2fanimations/15.1.4/15.1.5) |
| [@angular/common](https://github.com/angular/angular) | dependencies | patch | [`15.1.4` -> `15.1.5`](https://renovatebot.com/diffs/npm/@angular%2fcommon/15.1.4/15.1.5) |
| [@angular/compiler](https://github.com/angular/angular) | dependencies | patch | [`15.1.4` -> `15.1.5`](https://renovatebot.com/diffs/npm/@angular%2fcompiler/15.1.4/15.1.5) |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/main/packages/compiler-cli) ([source](https://github.com/angular/angular)) | devDependencies | patch | [`15.1.4` -> `15.1.5`](https://renovatebot.com/diffs/npm/@angular%2fcompiler-cli/15.1.4/15.1.5) |
| [@angular/core](https://github.com/angular/angular) | dependencies | patch | [`15.1.4` -> `15.1.5`](https://renovatebot.com/diffs/npm/@angular%2fcore/15.1.4/15.1.5) |
| [@angular/forms](https://github.com/angular/angular) | dependencies | patch | [`15.1.4` -> `15.1.5`](https://renovatebot.com/diffs/npm/@angular%2fforms/15.1.4/15.1.5) |
| [@angular/platform-browser](https://github.com/angular/angular) | dependencies | patch | [`15.1.4` -> `15.1.5`](https://renovatebot.com/diffs/npm/@angular%2fplatform-browser/15.1.4/15.1.5) |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular) | dependencies | patch | [`15.1.4` -> `15.1.5`](https://renovatebot.com/diffs/npm/@angular%2fplatform-browser-dynamic/15.1.4/15.1.5) |

---

### Release Notes

<details>
<summary>angular/angular</summary>

### [`v15.1.5`](https://github.com/angular/angular/blob/HEAD/CHANGELOG.md#&#8203;1515-2023-02-15)

[Compare Source](angular/angular@15.1.4...15.1.5)

##### forms

| Commit | Type | Description |
| -- | -- | -- |
| [5f2a3edcf2](angular/angular@5f2a3ed) | fix | Make radio buttons respect `[attr.disabled]` ([#&#8203;48864](angular/angular#48864)) |

#### Special Thanks

AleksanderBodurri, Alvaro Junqueira, Dylan Hunn, Joey Perrott, Matthieu Riegler, PaloMiklo and Paul Gschwendtner

<!-- CHANGELOG SPLIT MARKER -->

</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.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiIzNC4xMzguMiIsInVwZGF0ZWRJblZlciI6IjM0LjEzOC4yIn0=-->

Co-authored-by: cabr2-bot <cabr2.help@gmail.com>
Reviewed-on: https://codeberg.org/Calciumdibromid/CaBr2/pulls/1786
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org>
Co-committed-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org>
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Mar 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: forms forms: ControlValueAccessor forms: disabling controls merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note target: patch This PR is targeted for the next patch release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants