Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I have a form with some text input fields and a dynamic list of items stored in the $scope of the controller, with some functions to add/remove items in the list. I want to invalidate the form until the items list reaches a predefined length.

So I created a formRepeat directive which takes a ngModel attribute and then use the ngModelController to invalidate the form.

http://plnkr.co/edit/jSFvak?p=preview

This works but I think it's not the better way to do that as the directive is not very flexible.

The easiest would be to invalidate the form in the controller with something like :

$scope.myForm.$valid = false;

But this doesn't work.

Is there a better way ?

share|improve this question

6 Answers 6

up vote 2 down vote accepted

Based upon your plunker. I'd use the following $watch function (similar to @NicolasMoise response)

$scope.$watch('items', function (items) {
    $scope.myForm.$setValidity('count', items.length >= 5);
}, true);

It's important to set the objectEquality flag to true so that the $watch will fire if any of the objects properties change

or, if only a shallow list (collection) comparison is made, use $watchCollection

$scope.$watchCollection('items', function (items) {
    $scope.myForm.$setValidity('count', items.length >= 5);
});

I've also never had any luck with $setValidity('$valid') or similar

share|improve this answer
    
Good idea to create a new 'error key', seems to be the cleanest way to handle this. Thanks ! –  lechariotdor Feb 9 at 8:29
    
how would you turn this into a custom validator? –  chovy Feb 15 at 21:40

I don't think it's necessary to use a directive in this case. Just have an ng-repeat for items and inside your controller something like this

$scope.$watch('items', function(val){
    if(val.length<5){
        //$scope.myForm should be available here
        $scope.myForm.setValidity('$valid');
        //add additional form validation ($dirty, setting back to $invalid, etc...)
    }
})
share|improve this answer
    
Tried it here (see plunker). Like this it doesn't do anything, if a replace the '<' with a '>' it does invalidate the form after 5 items have been added... I didn't got any good result by replacing '$valid' by '$invalid'. After some thinking, I don't think it's a good idea to validate the form itself in the controller, as it would 'shadow' all the other input fields validation rules... –  lechariotdor Mar 4 '14 at 16:35

maybe it is what your looking for http://docs.angularjs.org/api/ng/type/form.FormController

share|improve this answer
    
Maybe I missed something but it seems we can only invalidate form controls with the form controller, not the form itself (setValidity only works on form controls). The problem is, I use ng-repeat to generate the DOM for the dynamic list stored as an array in the scope, so there isn't any form control I can use FormController on. –  lechariotdor Mar 4 '14 at 15:58
    
$scope.myForm should be available inside your controller. However, AFAIK you can't access inside directives (your own custom or inside ng-repeat) –  NicolasMoise Mar 4 '14 at 16:09
    
You can also set validity for the entire form, not sure how it reacts when there are conflicts. –  NicolasMoise Mar 4 '14 at 16:28

I think you can achieve this using ng-class.

Try this , In HTML,

<html data-ng-app="myApp">

  <head>
    <link data-require="bootstrap-css@*" data-semver="3.1.1" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
    <script data-require="angular.js@*" data-semver="1.2.13" src="http://code.angularjs.org/1.2.13/angular.js"></script>
    <script data-require="angular-animate@*" data-semver="1.2.13" src="http://code.angularjs.org/1.2.13/angular-animate.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body data-ng-controller="MainCtrl">
    <div class="container">
      <div class="col-sm-12 col-md-6 col-md-offset-3">
        <form name="myForm" ng-class="formClass">
          <div class="form-group">
            <label>Name</label>
            <input type="text" name="name" data-ng-model="user.name" class="form-control" required="" />
          </div>
          <div class="form-group">
            <label>Country</label>
            <input type="text" name="country" data-ng-model="user.country" class="form-control" required="" />
          </div>
          <div class="form-group">
            <label>Items</label>
            <br />
            <button type="button" class="btn btn-primary" data-ng-click="addItem()">Add</button>
            <p data-ng-show="items.length < min">I need at least {{min}} items ! (so {{min - items.length}} more would be good)</p>
            <div data-ng-repeat="item in items">
              <button type="button" class="btn btn-danger" data-ng-click="removeItem($index)">Delete</button>
              <span>{{item}}</span>
            </div>
          </div>
        </form>
      </div>
    </div>
  </body>

</html>

In your script.js,

angular.module('myApp', ['ngAnimate']);

angular.module('myApp')
  .controller('MainCtrl', ['$scope', function ($scope) {
    $scope.items = [];
    $scope.min = 5;
    var _counter = 0;
     $scope.formClass="invalid";
    $scope.addItem = function () {
      $scope.items.push('item' + _counter);
      _counter++;
     $scope.isFormValid();
    };
    $scope.isFormValid=function(){
       if ($scope.items.length < 5) {
        $scope.formClass="invalid";
      } 
      else if ($scope.items.length >=5){
         $scope.formClass="valid";
      }
    }
    $scope.removeItem = function (index) {
      $scope.items.splice(index, 1);  
      $scope.isFormValid();
    };

  }]);

In css file,

body {
  padding: 16px;
  background: #555555;
}

/*.my-form {
  transition:0.5s linear all;
  padding: 16px;
  border-radius: 4px;
  background: #ffffea;
}*/

.invalid {
    transition:0.5s linear all;
  padding: 16px;
  border-radius: 4px;
  background: #ffffea;
  background: #ffeaea;
}

.valid {
    transition:0.5s linear all;
  padding: 16px;
  border-radius: 4px;
  background: #ffffea;
  background: #eaffea;
}

Do you want something like this?.

Please have a look at this plunker

share|improve this answer
    
Thanks. It's working but it's just a visual trick. What I try to do is to make the form itself aware that something is wrong, like any other form error. –  lechariotdor Jan 12 at 8:07

a bit late, but I do that like this:

<button type="button" class="btn btn-success" 
        ng-disabled="form.$invalid || user.groups.length == 0>
        Submit
</button>
share|improve this answer
    
Thanks. It's working but it's just a visual trick like the other answer. What I try to do is to make the form itself aware that something is wrong, like any other form error (wrong input size, wrong email format, ...). –  lechariotdor Jan 12 at 8:09

The best way to do so (IMO) is to create a custom directive that uses the ngModelController Validators.

Validators get executed every time there is an update on the model and are used for form validity. Your directive may look something like this:

angular.module('directiveTest', []).directive('minLength', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ngModel) {
  scope.minlength = attrs.minLength || 1;
  ngModel.$validators.minLength  = function(modelValue){
    /* Assume TRUE when empty, as ngRequired should be used for mandatory values */
    return (ngModel.$isEmpty(modelValue)) ? true : (modelValue.length >= scope.minlength);
  };
}
};
});

And can call it from your HTML like this:

<input type="text" name="content" ng-list min-length="2" ng-model="content" />

You can find the working example on the following Plunker.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.