How To Disable Angular ReactiveForm Input Based On Selection

This post will explore how to disable a form <input> based on the input value of another form field in an Angular application. This is particularly useful if you are using Angular's Reactive Forms to handle user input, validation and model building before submitting to an endpoint.

If you have used Reaactive Forms previously, you will be aware that empty values are still populated and validated based on the Validators applied to a FormControl. Using the method below will allow you to programmatically exclude fields from validation and the object model created by the Angular Reactive Form.

How to disable an Angular ReactiveForm Input based on another input

This tutorial will follow the example of a component used to enter an address. If the user selects United States from the country dropdown, the State field is active. If the user selects any other country, the State input field will automatically be disabled. If the user re-selects the United States from the country dropdown, the State field will be enabled once more.

Step 1: Assuming you have a component shell ready, import the following libraries:

import { Component, OnInit, Input, EventEmitter } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

Step 2: Declare and initialise the following class variables. What you initialise countries and states to will depend on your application. Below is just a simple example.

export class AddressComponent implements OnInit {
    addressForm: FormGroup;
    states: Array;
    countries: Array;

    constructor(private formBuilder: FormBuilder) {
        this.countries = [{id: 'USA', name: 'United States'}, {id: 'UK', name: 'United Kingdom'}, {id: 'FR', name: 'France'}];
        this.states = [{ id: "AL", name: "Alabama" }, { id: "AK", name: "Alaska" }, { id: "AZ", name: "Arizona" }, { id: "AR", name: "Arkansas" }];
    }
}

Step 3: Next create a function to initialise the ReactiveForm (called addressForm in this example). My preference is to implement the ngOnInit function and then call seperate functions to initialise the form:

ngOnInit() {
    this.initAddressForm();
}

initAddressForm() {
    this.addressForm = this.formBuilder.group({
        addressLine1: ['', Validators.required],
        addressLine2: [''],
        city: ['', Validators.required],
        state: ['', Validators.required],
        postalCode: ['', Validators.required],
        country: ['', Validators.required]
    });
  }

In the initialisation of the addressForm I have kept all the default values blank and made all fields required except Address Line 2. We have now completed the basic set up of our address component. Everything until now should not be new to you if you have used Angular's ReactiveForms previously.

Step 3: Now use the power of Observables to watch for changes to the form. Essentially, a subscription will be set up to execute a function every time the value of Country is changed. This is much more efficient than the AngularJS method of using the digest cycle to poll the form. It is also a lot cleaner and less intessive on the browser to use Observable invocations as they only fire when the form changes.

In some scenarios you may want to watch the entire form, but in this case we are fine to just subscribe to the Country field.

Once again, I prefer to break up my initialisation logic in to seperate functions. Declare a function to watch the state field called onChanges() and then called it from ngOnInit() with this.onChanges().

onChanges() {
    this.addressForm.get('country').valueChanges
    .subscribe(selectedCountry => {
        if (selectedCountry != 'USA') {
            this.addressForm.get('state').reset();
            this.addressForm.get('state').disable();
        }
        else {
            this.addressForm.get('state').enable();
        }
    });
}

The onChanges() function is simple. It gets the country control within the addressForm and subscribes to any value changes. Whenever the value of the Country field changes, the function is executed.

The input to the function is the new value of the Country field. Use an if-statement to check the selected value for the country. If the value is not "USA" (as defined in our contructor), reset the state control to clear the input value and validation flags and then disable the control too.

Else, if the value is "USA", enable the state control.

The enable() and disable() methods are simply wrappers for boolean properties on the FormControl. When using ReactiveForms in HTML, Angular adds the disabled class to any input elements which have been disabled.

Step 4: (Optional). If you are using Bootstrap 3, you will have default support for disabled input controls. You can implement the AddressComponent as:

<form class="container-fluid" [formGroup]="addressForm">
    <div class="row">

        <div class="col-xs-12 form-group">
            <label for="country">Country:</label>
            <select class="form-control" id="country" formControlName="country">
                <option value="" disabled selected>Please select</option>
                <option *ngFor="let country of countries" [value]="country.id">{{country.name}}</option>
            </select>
        </div>

        <div class="col-xs-12 col-md-6 form-group">
            <label for="addressLine1">Address Line 1:</label>
            <input type="text" class="form-control" id="addressLine1" formControlName="addressLine1">
        </div>

        <div class="col-xs-12 col-md-6 form-group" *ngIf="addressForm.contains('addressLine2')">
            <label for="addressLine2">Address Line 2:</label>
            <input type="text" class="form-control" id="addressLine2" formControlName="addressLine2">
        </div>

        <div class="col-xs-12 col-md-6 form-group">
            <label for="city">City:</label>
            <input type="text" class="form-control" id="city" formControlName="city">
        </div>

        <div class="col-xs-12 col-md-6 form-group">
            <label for="state">State:</label>
            <select class="form-control" id="state" formControlName="state">
                <option value="" disabled selected>Please select</option>
                <option *ngFor="let state of states" [value]="state.id">{{state.name}}</option>
            </select>
        </div>

        <div class="col-xs-12 col-md-6 form-group">
            <label for="postalCode">Postal Code:</label>
            <input type="text" class="form-control" id="postalCode" formControlName="postalCode">
        </div>

    </div>
</form>