AngularJS not-in-list validation

Posted by

Recently I had to make a form where the one of the underlying data columns has a unique constraint on it. The data isn’t very sensitive and doesn’t change very often, but I wanted to provide functionality to validate client side first, before actually submitting the form to the server. In this specific use case, I have to get the list of the strings in the specific column I want, then compare the entered text to the list.

My first approach was to simply do the comparison every time the input changes, using ng-change and $setValidity on the input. By doing this, you can then provide feedback using the $error object and check for the provided error definition. In the following example, that is “alreadyExists”. Documentation for $setValidity can be found here.

vm.keyChanged = function () {
  if(vm.model.key != null)
    vm.model.key = vm.model.key.toUpperCase();
  $scope.mf.modalForm.key.$setValidity("alreadyExists", vm.keys.indexOf(vm.model.key) < 0);
};

 

<input maxlength="2" required ng-change="mf.keyChanged()" />
<small class="error" ng-show="mf.modalForm.key.$error.alreadyExists">
  This key already exists
</small> 

While leveraging ng-change and $setValidity does technically work, I figured it would be better to put that functionality into a reusable directive. This means I can add an attribute to an input, provide a list, and it will handle the validation on that input.

app.directive('notInList', function() {
  return {
    require: 'ngModel',
    scope: {
      notInList: '='
    },
    link: function(scope, element, attrs, control) {
      var list;

      scope.$watchCollection('notInList', function (newList, oldList) {
          list = newList;
      });

      if(scope.notInList !== undefined){
        control.$validators.isInList = function(modelValue, viewValue) {
  
          if (control.$isEmpty(modelValue)) // if empty, correct value
          {
            return true;
          }
  
          if (list.indexOf(modelValue) != -1) // Is already in list value
          {
            return false;
          }
          return true; // correct value
        };
      }
    }
  };
});

Using this directive, you pass in an array of strings to compare to, and the $error object gets “isInList” set if the input matches one of the elements of the array. In the following example, directive “not-in-list” is added to myInput, and “testList” is an array passed in to that directive.

Your (case sensitive) input: 
<input type="text" name="myInput" ng-model="myInput" not-in-list="testList" />
<span style="color:red;" ng-show="myForm.myInput.$error.isInList">
  This is already in the list
</span>

Below is a plunker of the not-in-list directive in action. There is also a second directive called not-in-list-ignore-case, which provides the same functionality, but ignores case. Note that the testList is defined in the controller as [‘test’, ‘list’, ‘testing’, ‘invalid’]. This means that if you enter any of those strings into the input, it will display the isInList error message.

One comment

  1. Thanks for a great component!

    When I had just:
    if(scope.notInList !== undefined){

    I got a console error of: TypeError: Cannot read property ‘indexOf’ of undefined

    When I changed it to:
    if(scope.notInList !== undefined){
    list = scope.notInList;

    It worked.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.