Home > Mobile >  Making input disable reactive in Angular
Making input disable reactive in Angular

Time:05-10

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:

  1. I'd have to call control.disable()/enable() every time the value of apiLoading() changes, this is possible via a directive as this blog suggests.
  2. 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. Whenever apiLoading() is false, I'd have set the control back to its last set disable state that wasnt set by apiLoading().

#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()" >
  • Related