3

Say I have a model object with the attribute favoriteColors

{
    ...
    favoriteColors: ['red', 'green', 'blue']
    ....
}

I expose them to the user with an ng-repeat

<form name="userForm">
    ...
    <ul>
        <li ng-repeat="color in user.favoriteColors">
            <input type="text" ng-model="color" />
            <a href="" ng-click="delete(color)">remove</a>
        </li>
    </ul>
    <a href="" ng-click="add()">Add a new favorite color</a>
    ...
</form>

I would like to be able to check the validity of the favoriteColors field doing something like this

<div ng-show="userForm.favoriteColors.$error">
    You must have at least one favorite color
</div>

It doesn't seem possible to do this using a built in validator, and I'm not sure on which element I would put a custom directive in order to get the ngModelController for favoriteColors.

4 Answers 4

13

My solution is add one hidden input tag and binding with length of array

    <form name="userForm">
      ...
      <ul>
        <li ng-repeat="color in user.favoriteColors">
          <input type="text" ng-model="color" />
          <a href="" ng-click="delete(color)">remove</a>
        </li>
      </ul>
      <a href="" ng-click="add()">Add a new favorite color</a>
      ...

      <!-- new line -->
      <input style="display:none" name="colorsLength" type="number" min=1 value="{{user.favoriteColors.length}}"/>
    </form>

So, you can use userForm.colorsLength.$error for your validation. Good luck!

Sign up to request clarification or add additional context in comments.

1 Comment

please take a look at my answer! stackoverflow.com/a/32503969/595152 what do you think?
11

@Loi Pham, unfortunately I am not allowed to comment, so I have to add a post. I like your approach. However I had to add ng-model to the input to make the validation work:

<input style="display: none" type="number" name="length" readonly ng-model="colors.length" min="1">

1 Comment

please take a look at my answer! stackoverflow.com/a/32503969/595152 what do you think?
4

There is another way by adding a directive:

/**
 * Validator for checking an array length.
 * @example
 *   <input ng-model="user.favoriteColors" validate-length />
 */
app.directive('validateLength', function() {
  return {
    require: 'ngModel',
    link: function(scope, element, attrs, ngModel) {

      // do not set invalid model to undefined, should stay []
      ngModel.$options = {
        allowInvalid: true
      };

      scope.$watch(function () { return ngModel.$modelValue && ngModel.$modelValue.length; }, function() {
        ngModel.$validate(); // validate again when array changes
      });

      ngModel.$validators.length = function() {
        var arr = ngModel.$modelValue;
        if(!arr) { return false; }

        return arr.length > 0;
      };

    }
  };
});

4 Comments

This is by far the best solution except that it creates an isolated scope. Thats going to get in the way in a lot of use cases. I'd change it to watch ngModel.$modelValue.length instead. Eg: scope.$watch(function (){ return ctrl.$modelValue.length; }, ...)
You are right, that would be better, I will update my answer when I have time to check this :) thanks!
I see you added a null check too, nice :)
I've updated the code by adding model options!! the model should allow invalid because the array should stay [] instead of be set to undefined!
-1

To have validation the way you are requesting, you have to use an ng-model to put your array of colors into the form so that the array can be validated.

Here's a quick example in a plunker where I push a validator on the $parsers pipeline of the ngModelController which will check the colors array length. Keeping the colorRequired directive separate will allow you to have situations where a color is not required. You could also add to that directive so it will take a boolean argument on the attribute so you can decide at run-time if a color should be required.

http://plnkr.co/edit/yFuSXxacSW811WfZqaPC

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

app.controller('MainCtrl', function($scope) {
  $scope.colors = ['red', 'blue', 'green'];
});

app.directive("colorGroup", [function() {
    "use strict";
    return {
        restrict: 'E',
        require: 'ngModel',
        template: '<ng-form name="userForm">\
               <ul>\
               <li ng-repeat="color in model">\
              <input type="text" ng-model="color" />\
              <a href="" ng-click="delete($index)">remove</a>\
              </li>\
              </ul>\
              </ng-form>',
        link: function($scope, element, attrs, ngModelCtrl)
        {
            $scope.$watch(function(){
                return ngModelCtrl.$modelValue;
            }, function(){
                $scope.model = ngModelCtrl.$viewValue;
            });

            $scope.delete = function(idx)
            {
                        ngModelCtrl.$viewValue.splice(idx, 1);
                        ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue);
                        $scope.model = ngModelCtrl.$viewValue;
            }
        }
    }
}]);

app.directive("colorRequired", function() {
    "use strict";
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function($scope, element, attrs, ngModelCtrl)
        {
                ngModelCtrl.$setValidity('colorrequired', (ngModelCtrl.$viewValue.length > 0));

                ngModelCtrl.$parsers.push(function(viewValue) {
                    var valid = viewValue.length > 0;
                    ngModelCtrl.$setValidity('colorrequired', valid);
                    return valid ? viewValue : undefined;
                });
            }
        }
    });

1 Comment

Comment from user3677208: There is one issue. If you edit your color to something else (not a color), your validation woud not work. It will not fire if the model was changed by input editor. Could you have any ideas how to fix if?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.