Exploring the difference between disabling a form control through reactive forms API and HTML attributes

Post Editor

Having parts of a form disabled is a common requirement for any large application. Sometimes users must be prevented from interacting with a form based on their role in an application.I n this article, we will explore different ways to disable reactive form controls.

7 min read
0 comments
post

Exploring the difference between disabling a form control through reactive forms API and HTML attributes

Having parts of a form disabled is a common requirement for any large application. Sometimes users must be prevented from interacting with a form based on their role in an application.I n this article, we will explore different ways to disable reactive form controls.

post
post
7 min read
0 comments
0 comments

Having parts of a form disabled is a common requirement for any large application. Sometimes users must be prevented from interacting with a form based on their role in an application; an input field can also be disabled because its value is pre-populated and no user input is required.

In this article, we will explore different ways to disable reactive form controls and see how they affect the form’s state (which in turn would affect our application state).
Below is a simple form with two fields and a button. We will use this setup to illustrate the different ways to disable form controls.

Here’s how we create this form in our application’s code:

Form component class
Link to this section

<>Copy
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit { form: FormGroup; constructor(private formBuilder: FormBuilder) { } ngOnInit(): void { this.form = this.formBuilder.group({ firstName: [{ value: 'Foo', disabled: true }, [Validators.required]], lastName: ['Bar'] }); } onSubmit(): void { } get firstName(): FormControl { return this.form.controls.firstName as FormControl; } get lastName(): FormControl { return this.form.controls.lastName as FormControl; } }

Note that in the component class I have defined accessors to the two form controls for the ease of access.

Form template
Link to this section

<>Copy
<form [formGroup]="form"> <h1>Angular Reactive Form</h1> <input formControlName="firstName" placeholder="First Name" /> <input formControlName="lastName" placeholder="Last Name" /> <button (click)="onSubmit()">Submit</button> </form>

I’m using formGroup and formControlName directives from Reactive Forms API here.

Using the FormControl’s disable() instance method
Link to this section

This method will disable both the UI control and the FormControl’s instance. Let us see how we’d use this method to disable a form control. In the ngOnInit function,  after creating the form instance, we can disable the lastName form control like below.

<>Copy
ngOnInit(): void { this.form = this.formBuilder.group({ firstName: [{ value: 'Foo', disabled: true }, [Validators.required]], lastName: ['Bar'] }); this.lastName.disable(); }

If we console.log the control lastName we’ll see its instance will be disabled.

Showing the affected properties for a disabled form control

This way is quick and straightforward.

When would this way of disabling FormControls be a disadvantage?
Link to this section

When we depend on a FormGroup’s validity status to make some decisions. Let’s see how, let’s change our ngOnInit code a bit by adding console logs.

<>Copy
ngOnInit(): void { this.form = this.formBuilder.group({ firstName: [{ value: 'Foo', disabled: true }, [Validators.required]], lastName: ['Bar'] }); this.lastName.disable(); console.log(this.lastName); console.log('Form:::', this.form); }

We’ve given the form controls default values, disabled both the controls and required the firstName control by having a required validator on its configuration. Note that the firstName control is disabled by default.

The last console.log’s output is below.

Showing how having all controls disabled in a form group affects the forms status and validity

Something worth noting is that when we have all form controls disabled, irrespective of their validity status', the controls' instance status properties will be set to "DISABLED" hence the FormGroup's status will also be set to "DISABLED".

In the log snippet above, can you also see that the form’s valid and invalid properties are both false, but the controls have values and are valid? Can you see the confusion this way of disabling controls might cause when the form’s validity is a dependency in some decisions we have to make?

For example, let's say we have a big form implementation and we are aware that some form fields will need to be disabled.

For instance, say we have a second form in a separate page that requires the firstName and lastName the user had entered in the first form. We would, in the second form, disable and pre-populate the firstName and lastName inputs; now let’s say we want to propagate the form’s value to some state store when the form value or status changes but when the form is valid.

Let’s consider a case where we have a listener to the form value changes and we have some controls whose values are changed by an external source.

<>Copy
this.form.valueChanges .pipe( distinctUntilChanged() ) .subscribe( (status) => { if (this.form.status === 'VALID') {// `this.form.status` is "DISABLED" // set some state value } } ); this.lastName.setValue('Baz');

We set an observer to the form’s value changes and after we change the value of the lastName control.

Our form’s value will not sync with our application state store because when the observer executes, it will find that the form’s status is DISABLED even though the form value is valid which might cause confusion and discrepancies in our code especially when the state store has observers to it also.

That could be an issue with this mechanism of disabling form controls.

Another way to disable form controls is the one we use to disable the firstName control when instantiating the form itself.

<>Copy
this.form = this.formBuilder.group({ firstName: [{ value: 'Foo', disabled: true }, [Validators.required]], lastName: ['Bar'] });

This has the same effect described above when a control is disabled using the .disable() control method.

Using a template attribute to disable FormControls
Link to this section

Yet another way is to add disabled=true attribute to HTML in the template like this:

<>Copy
<form [formGroup]="form"> <h1>Angular Reactive Form</h1> <input formControlName="firstName" placeholder="First Name"> <input formControlName="lastName" disabled="true" placeholder="Last Name"> <button (click)="onSubmit()">Submit</button> </form>

Which gives the below warning

This warning is thrown to warn you about the potential errors regarding "changed after checked" that might be thrown in result of using the disabled attribute directive with a reactive form.

What this means is that, if the disabled attribute would expect a dynamic expression like [disabled]="isDisabled"   (resulting in true or false, not just a static true for example) resulting in a binding eligible for being checked during change detection, it would make the binding susceptible to the "changed after checked" error, that is, the expression could be false when change detection is run and then true when the change detection verification phase runs.

A deep dive into the ‘ExpressionChangedAfterItHasBeenCheckedError’ error can be found here .

So to avoid the warning, you can use the [attr.*] binding like this:

<>Copy
<form [formGroup]="form"> <h1>Angular Reactive Form</h1> <input formControlName="firstName" placeholder="First Name"> <input formControlName="lastName" [attr.disabled]="true" placeholder="Last Name"> <button (click)="onSubmit()">Submit</button> </form>

There’s no warning, but it is worth noting that this will add the HTML disabled attribute which cannot be toggled using just true and false (as false also results in a disabled field), null and undefined can be used to enable the field. Another way to enable a field with this attribute with value set to true, false or any truthy value is by removing it from the field.

Both approaches have the same outcome, a form with a status of VALID, which is what we want to offset the limitation that comes with using the first approach described above (Using the FormControl’s disable() instance method).

The form's value is missing the firstNames' property and value

However, it is important to note the form’s value does not have the firstName’s control value because it is disabled, this happens when some of the form controls are disabled (Using the FormControl’s disable() instance method or from the form configuration like for the firstName control) and some are enabled or disabled using the [disabled] template directive.

To circumvent this issue, we can use a form’s instance method to get the raw form value that will include values of disabled controls on the form.

<>Copy
this.form.getRawValue()
<>Copy
console.log(this.form.value); console.log(this.form.getRawValue()); // The logs will have
The results of using a FormGroup's .value and getRawValue properties

With the second approach (Using a template attribute to disable FormControls) we can expect what we see on the form UI to mirror the value of the form’s instance without affecting the controls instances.

Another way similar to the latter ([attr.disabled]) is to use the readonly attribute.

<>Copy
<form [formGroup]="form"> <h1>Angular Reactive Form</h1> <input formControlName="firstName" placeholder="First Name"> <input formControlName="lastName" readonly="true" placeholder="Last Name"> <button (click)="onSubmit()">Submit</button> </form>

This one has the same effect as using the disabled attribute but does not apply disabled visuals to the control, this will disable the input control but to the eye it will look like the control is enabled.

You can simply style the input like below.

<>Copy
input { margin-bottom: 8px; width: 250px; height: 45px; border-radius: 3px; padding-left: 10px; border: 1px solid rgba(0, 0, 0, 0.4); &:read-only { color: rgb(84, 84, 84); cursor: default; background-color: rgba(239, 239, 239, 0.3); &:focus { outline: none; } } }

Those are the differences I have experienced with the different ways of disabled form controls.

In conclusion
Link to this section

We have seen the different ways we can use to disable form control. They are the following:

  • Using the FormControl’s disable() instance method
  • Disabling a FormControl from the form configuration
  • Using a template attribute to disable FormControls like disable, [attr.disable] and readonly

We have also seen how the first two approaches can affect the form’s status value (Which could possibly affect the application state if it is synced with the form’s state).
Depending on your requirements and use case, it is beneficial to consider beforehand the approach that will not limit your flexibility when working with reactive forms.

The Git repo for the demo project is here.

Comments (0)

Be the first to leave a comment

Share

About the author

author_image
author_image

About the author

Thabo Ambrose

About the author

author_image
Looking for a JS job?
Job logo
Senior Full-Stack Developer (Node+Angular)

A-listware

Ukraine
Remote
$60k - $66k
Job logo
Full Stack Java/Angular Developer

Black Knight

Worldwide
Remote
$70k - $90k
Job logo
Angular Developer

Ziras Technologies

United States
Remote
$58k - $145k
More jobs
NxAngularCli
NxAngularCli
NxAngularCli

Featured articles