I'm having a hard time putting an additional disable condition on an input in Angular. Imagine a basic control:
<input [formControl]="control">
I know that to disable or enable this input, it's normally a simple call to enable()
or disable()
.
I'm working on a really large codebase. In many places control.disable()
is called on the control or parent controls to force the disabling of the input.
I'd like for the input to be temporarily disabled while an API (possibly multiple) is loading in the background, i.e:
<input [formControl]="control" [disabled]="control.disabled || apiLoading()" >
However Angular gives me a warning, because it doesn't want me to bind to disabled
and instead wants me to call control.disable()
. This is really awkward for me:
- I'd have to call
control.disable()/enable()
every time the value ofapiLoading()
changes, this is possible via a directive as this blog suggests. - But like I said, I'm dealing with a massive codebase.
control.disable()
is already called all over the place, at unpredictable times, and it takes priority. WheneverapiLoading()
is false, I'd have set the control back to its last set disable state that wasnt set byapiLoading()
.
#2 has been a really hard to implement for me. I have to keep the last set state, which I tried via a combination of control.statusChange
and emitEvent: false
, but ultimately hit conflicts when statusChange
events got emitted when the control value changed. I started to go down a rabbit hole.
I just need something simple: temporary disabling while APIs are loading to apply on top of disabling done elsewhere in the codebase, but because I'm forced to direct my disabling via enable()/disable()
, I have to be extra careful not to undo disable()
s already called elsewhere in the huge codebase I'm working on.
What is the simplest way to achieve something like the following without having to drop the use of formControl
(which does touched/dirty detection nicely)?
<input [formControl]="control" [disabled]="control.disabled || apiLoading()" >
CodePudding user response:
apiLoading() Loading should return an Observable then use async pipe to consume it:
<input [formControl]="control" [disabled]="control.disabled || apiLoading()|async" >
CodePudding user response:
I've found a way I'm not entirely happy with but does work. With a custom directive, I can no-op the setDisabledState
on the value accessor, but hold on to the original in a variable. That way I can disable the input based on my custom condition - not by having to call disable()/enable()
- and the formControlDirective
has no control on the disabling.
import { FormControlDirective } from "@angular/forms";
import { Directive, Input } from "@angular/core";
/**
* This directive helps to provide a custom method for disabling a form control
* when using the FormControl directive, rather than managing the disabling of
* the control via the enable/disable methods - which can be quite impractical.
*/
@Directive({
// eslint-disable-next-line @angular-eslint/directive-selector
selector: "[customDisable]",
})
export class CustomDisableDirective {
disabler: (boolean) => void;
@Input() set customDisable(condition: boolean) {
this.disabler(condition);
}
constructor(private dir: FormControlDirective) {
// No-ops the accessor disable so that the formControl directive has
// no effect over the disabling, and disabling is controlled here.
const accessor = this.dir.valueAccessor;
this.disabler = accessor.setDisabledState.bind(accessor);
this.dir.valueAccessor.setDisabledState = () => {};
}
}
Then in the html:
<input [formControl]="control" [customDisable]="control.disabled || apiLoading()" >