Search code examples
javascriptangularjsvalidationng-pattern

Force ngPattern validation when another input is changed


I have an Angular JS application in which there is a required phone number field, as well as a country select field. The country select defaults to United States. When the selected country is United States or Canada, I want to validate the phone number field with a domestic phone number pattern, but when any other country is selected, I don't want to validate the pattern for the phone number.

To accomplish this desired functionality, I used a similar solution to this (the dirty way). Instead of checking a requireTel boolean value, I check whether the countryCode is USA or CAN to determine whether to validate with my phone number pattern.

This solution works, but only if you change the phone number after you change the country. For example, if you have United States selected, and type in a long international phone number, like +441234567890, it uses the domestic phone number validation and shows Invalid pattern. But when I change the country to Bahamas, it should no longer use the domestic phone number validation, and the Invalid pattern message should go away. However, the Invalid pattern message is still shown until I change the phone number. Once I change the phone number, the message goes away.

The reason this is happening is because the ng-pattern validation is not performed on the phone number field again when the country select is being changed. Is there a way to force the ng-pattern validation to be re-evaluated whenever the country is changed?

See the snippet below to reproduce the issue.

var app = angular.module('example', []);

app.controller('exampleCtrl', function($scope) {
  $scope.countries = [{
    countryName: 'United States',
    countryCode: 'USA'
  }, {
    countryName: 'Canada',
    countryCode: 'CAN'
  }, {
    countryName: 'Bahamas',
    countryCode: 'BHS'
  }, {
    countryName: 'Chile',
    countryCode: 'CHL'
  }];

  $scope.selectedCountry = $scope.countries[0];

  $scope.phoneNumberPattern = (function() {
    var regexp = /^\(?(\d{3})\)?[ .\/-]?(\d{3})[ .-]?(\d{4})$/;
    return {
      test: function(value) {
        var countryCode = $scope.selectedCountry.countryCode;
        if (countryCode !== 'USA' && countryCode !== 'CAN') {
          return true;
        }
        return regexp.test(value);
      }
    }
  })();

});
.error {
  color: red;
  font-style: italic;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="example" ng-controller="exampleCtrl">
  <ng-form name="exampleForm">
    <label>Country:</label>
    <select ng-model="selectedCountry" ng-options="country as country.countryName for country in countries">
    </select>
    <label>Phone:</label>
    <input type="text" ng-model="phone" name="phone" ng-pattern="phoneNumberPattern" required>
    <small class="error" ng-show="exampleForm.phone.$error.pattern">Invalid pattern.</small>
    <small class="error" ng-show="exampleForm.phone.$error.required">Phone number is required.</small>
  </ng-form>
</div>


Solution

  • Here's another solution. Using a function that returns a pattern.

    Live example on jsfiddle.

    angular.module('ExampleApp', [])
      .controller('ExampleController', function($scope) {
        $scope.countries = [{
          countryName: 'United States',
          countryCode: 'USA'
        }, {
          countryName: 'Canada',
          countryCode: 'CAN'
        }, {
          countryName: 'Bahamas',
          countryCode: 'BHS'
        }, {
          countryName: 'Chile',
          countryCode: 'CHL'
        }];
    
        $scope.selectedCountry = $scope.countries[0];
        var regUSA = /^\(?(\d{3})\)?[ .\/-]?(\d{3})[ .-]?(\d{4})$/;
        var regAll = /.*/;
        $scope.phoneNumberPattern = function() {
          var countryCode = $scope.selectedCountry.countryCode;
          if (countryCode !== 'USA' && countryCode !== 'CAN')
            return regAll;
          else
            return regUSA;
        }
      });
    .error {
      color: red;
      font-style: italic;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.js"></script>
    <div ng-app="ExampleApp">
      <div ng-controller="ExampleController">
        <ng-form name="exampleForm">
          <label>Country:</label>
          <select ng-model="selectedCountry" ng-options="country as country.countryName for country in countries">
          </select>
          <label>Phone:</label>
          <input type="text" ng-model="phone" name="phone" ng-pattern="phoneNumberPattern()" required>
          <small class="error" ng-show="exampleForm.phone.$error.pattern">Invalid pattern.</small>
          <small class="error" ng-show="exampleForm.phone.$error.required">Phone number is required.</small>
        </ng-form>
      </div>
    </div>

    NOTE: It is important that the regular expression variables are declared outside the function. If you try to change the method in the following way:

    if (countryCode !== 'USA' && countryCode !== 'CAN')
        return /.*/;
    else
        return /^\(?(\d{3})\)?[ .\/-]?(\d{3})[ .-]?(\d{4})$/;
    

    It will lead to an infinite $digest error.