angular: Bug: setting `[disabled]` attribute no longer works with `formControlName`
Command
other
Is this a regression?
- Yes, this behavior used to work in the previous version
The previous version in which this bug was not present was
Angular 14
Description
When upgrading from Angular 14 to Angular 15 the [Disabled] directive of form controls stopped working.
Is this a bug or did something change?
I’m using version 15.0.2
Minimal Reproduction
<button type=“button” id=“btnAddPeopleEditor” class=“btn btn-success btn-sm” (click)=“addPeople(1)” [disabled]=“disabledEditEditor || (habilitarControle(isReadOnly) == ‘true’)”> Add </button>
Exception or Error
No error occurs. It simply stopped working.
Your Environment
Angular CLI: 15.0.2
Node: 16.17.0
Package Manager: npm 8.18.0
OS: win32 x64
Angular: 15.0.2
... animations, cli, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, platform-server
... router
Package Version
---------------------------------------------------------
@angular-devkit/architect 0.1500.2
@angular-devkit/build-angular 15.0.2
@angular-devkit/core 15.0.2
@angular-devkit/schematics 15.0.2
@angular/cdk 15.0.1
@angular/material 15.0.1
@nguniversal/builders 15.0.0
@nguniversal/common 15.0.0
@nguniversal/express-engine 15.0.0
@schematics/angular 15.0.2
rxjs 7.5.7
typescript 4.8.4
Anything else relevant?
No response
About this issue
- Original URL
- State: open
- Created 2 years ago
- Reactions: 14
- Comments: 39 (12 by maintainers)
Commits related to this issue
- fix(datagrid): disable row checkbox/radio using `disabled` property This is a workaround for a regression that was introduced in Angular v15. See angular/angular#48350 for details. — committed to vmware-clarity/ng-clarity by kevinbuhmann a year ago
- fix(datagrid): disable row checkbox/radio using `disabled` property This is a workaround for a regression that was introduced in Angular v15. See angular/angular#48350 for details. closes #520 — committed to vmware-clarity/ng-clarity by kevinbuhmann a year ago
- fix(datagrid): disable row checkbox/radio button in Angular v15 This is a workaround for a regression that was introduced in Angular v15. See angular/angular#48350 for details. closes #520 — committed to vmware-clarity/ng-clarity by kevinbuhmann a year ago
- fix(datagrid): disable row checkbox/radio button in Angular v15 This is a workaround for a regression that was introduced in Angular v15. See angular/angular#48350 for details. closes #520 — committed to vmware-clarity/ng-clarity by kevinbuhmann a year ago
- fix(datagrid): disable row checkbox/radio button in Angular v15 This is a workaround for a regression that was introduced in Angular v15. See angular/angular#48350 for details. closes #520 — committed to vmware-clarity/ng-clarity by kevinbuhmann a year ago
- fix(datagrid): disable row checkbox/radio button in Angular v15 This is a workaround for a regression that was introduced in Angular v15. See angular/angular#48350 for details. This is a backport of... — committed to vmware-clarity/ng-clarity by kevinbuhmann a year ago
- fix(datagrid): disable row checkbox/radio button in Angular v15 This is a workaround for a regression that was introduced in Angular v15. See angular/angular#48350 for details. This is a backport of... — committed to vmware-clarity/ng-clarity by kevinbuhmann a year ago
I want to echo @e-oz here. This is not a good solution in my opinion. We can set reactive form validators in our templates using native HTML attributes like
required
,min
,max
, etc. Why shoulddisabled
be different?[attr.disabled] and [disabled] doesn’t add “disabled” attribute anymore. Quite important change, actually, please mention such changes as “breaking”.
About the
attr.disabled
“bug”, this a breaking change/feature, the “fix” is to opt-out from the new behaviour by importing theReactiveFormsModule
with a config.Hi everyone. Indeed, I’ve tracked this down, and @JeanMeche is correct. While trying to fix a bug in https://github.com/angular/angular/pull/47576, I accidentally caused this related breakage. For now, you can work around by opting out when you import forms:
or, if you’re using template-driven forms:
I expect to have this fixed for real in a patch release soon.
We can declare reactive form attributes in the template, but only some of them - that’s not cool. It is not always possible (or brings a lot of code) to modify some attributes based on input values - because they might change. When it’s in the template, Angular change detection will monitor the changes and update bindings, when it’s in the code - you have to do this, and it’s not always trivial.
Here is a directive as a workaround:
After considerable debugging, I got to the bottom of this.
tl;dr
This behavior change was caused by a fix to make
setDisabledState
always called. Previously, using[attr.disabled]
caused your view to be out of sync with your model.disabled
on your model, not your template. Trynew FormControl({value: 'foo', disabled: true})
. Or you could callmyControl.disable()
inngOnInit
.15.1.0
or later and importFormsModule.withConfig({callSetDisabledState: 'whenDisabledForLegacyCode'})
(orReactiveFormsModule
, if that’s what you’re using).[attr.disabled]
on radio buttons? This is currently broken, and will be fixed soon in #48864.Full Explanation
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, in #47576.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.) This resulted in a mismatch between the model and the DOM – the model would think the control was enabled, when it was actually disabled. Now, becausesetDisabledState
is always called, the value in the DOM will be immediately overwritten with the “correct” enabled value.Users of Reactive Forms 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 callingFooControl.disable()
inngOnInit
.)If this incompatibility is too breaking, you may also opt out using
FormsModule.withConfig
orReactiveFormsModule.withConfig
at the time you import the module, via thecallSetDisabledState
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 via the model. (This is probably a design oversight in the forms package!) In #48864, we have special cased radio buttons to ignore their first call tosetDisabledState
when incallSetDisabledState: 'always'
mode. This preserves the old behavior, allowing[attr.disabled]
to keep working.Hi @jnizet
the bug occurs when using the formControlName within the formGroup.
You can look in the ‘profile-editor.component.html’ component and compare two controls:
<input [disabled]="true" id="street" type="text" formControlName="street" />
Does not work.
<input [disabled]="true" id="text_disable1" type="text"/>
It works
Here’s an example:
https://stackblitz.com/edit/angular-cu6mce?file=src/app/profile-editor/profile-editor.component.html
We encountered this issue too when setting
disabled
attribute on template driven select/input[checkbox].We have dynamic access rights and in ngAfterViewInit() we set the attribute when in read-only mode. It worked in ang14, but with ang15 it’s stopped in our template driven forms. Our reactive ones works fine. This is with latest 15.2.8
The
disabled
attribute never get set. It’s like the angular code removes it. Our directive sets it like this;I also tried this. Works with reactive, but not working in template:
I even tried this and the same. Works with reactive, but not template.
The only way to get it working again was to disable the new disable functionality with:
I much prefer to have the new functionality but cannot figure out how to disable our template driven controls.
Thanks for the reproduction @leandrotassi. This is a serious regression that is likely to break a lot of forms in many apps.
The only PR touching the
formControlName
directive in v15 was #43499 by @crisbeto. This could be the cause – I’ll have to write a test to check.@polonmedia
I have the same issue so for the workaround i have used [readonly]
@dylhunn In the original question a button was used, but all the answers look like they are using an input with a FormControl, which a button can’t use.
I have a component in which a button is used with the disabled attribute and this no longer works. If I use the withConfig fix in the module where my module of the component is imported it doesn’t fix it.
Any idea on a solution for disabled attribute in buttons?
@benartmon Far too many projects rely on the old behavior, including thousand of tests inside of Google. We may or may not deprecate it at some distant future point, but the option will never be removed.
I think that this PR actually introduced a regression @dylhunn, as now I cannot disable a single radio button anymore - the only way is to use the old behavior via
callSetDisabledState: 'whenDisabledForLegacyCode'
.Take the example in the RadioControlValueAccessor docs, how could we disable individual radio buttons? In Angular 14 I used to do it via
[attr.disabled]
, but in v15 that doesn’t work anymore. Disabling the whole control is not an option, as it disables all the radios, so how would we (with this new way) disable radio inputs individually? It’s a valid use case, right?Here is StackBlitz with reproduction: https://stackblitz.com/edit/angular-ivy-jcwird?file=src/app/app.component.html