Search code examples
javascriptangularjsformsangular-ngmodel

Angular Forms. How to Get Some Inputs to Reevaluate Others?


I have a form in angular that that a Category text input and an ID input. The ID must be unique to the category. So if I have the following items for Cat1:

Cat1 - 123
Cat1 - 245
Cat1 - 456

Then If i tried to enter a new item with the category Cat1 and the id 123 then that input wouldn't be unique to the category and the form could not be submitted. If the id changed to 1234 then it would be valid.

To achieve this I have a custom directive called unique that required ngModel. It looks like this-

HTML-

<input name="ID"
       required
       unique="id in getItemsByCategory(newCategory)"
       type="text"
       ng-model="newId"></input>

JS-

.directive('unique', function() {
    return {
      require: '^ngModel',
      link: function(scope, ele, attr, ngModelController) {
        ngModelController.$validators.unique = function(val) {
          var uniqueInRegex = /([^\s]+)\s+in\s([^\s]+)/i;

          var matches = attr.unique.match(uniqueInRegex);
          var property = matches[1];
          var collection = scope.$eval(matches[2]);

          for(var i = 0; i < collection.length; i++) {
            var item = collection[i];
            if(item[property] === val) {
              console.log('not unique');
              return false;
            }
          }

          console.log('unique');
          return true;
        };
      }
    };
  });

Which works fine:

enter image description here

But if the validator finds the field is not unique based on the category, and the category field changes, then the validator doesn't reevaulate.

enter image description here

Here is a JSBin of what what's seen in these gifs:

http://jsbin.com/wubesutugo/edit?html,js,output

Is there anyway I can have one input "touch" another input when it changes? I want this validation directive to be generic because i have multiple places I'd like to use it. So I don't want this logic in the controller.


Solution

  • Your solution with the ng-change is sufficient. An alternative would be using a watch ($watchCollection, to be precise):

    .directive('unique', function() {
        var uniqueInRegex = /([^\s]+)\s+in\s([^\s]+)/i;
        return {
          require: '^ngModel',
          link: function(scope, ele, attr, ngModelController) {
            var matches = attr.unique.match(uniqueInRegex);
            var property = matches[1];
            var collectionExpr = matches[2];
    
            scope.$watchCollection(collectionExpr, function(newval) {
              ngModelController.$validate();
            });
    
            ngModelController.$validators.unique = function(val) {
              var collection = scope.$eval(collectionExpr);
              for(var i = 0; i < collection.length; i++) {
                var item = collection[i];
                if(item[property] === val) {
                  console.log('not unique');
                  return false;
                }
              }
    
              console.log('unique');
              return true;
            };
          }
        };
    })
    

    (Also demonstrates calculating the regular expression just once.)

    Try it: http://jsbin.com/wiyugozuje/1/edit?js,console,output