30
votes

I try to create a directive which should peform some actions when an input field is marked as invalid. For this example lets assume I have a directive which checks if the input is a prime number, and I want to create a directive which adds a class to the element when it's invalid:

<input type="text" ng-model="primeNumber" validate-prime invalid-add-class="error">

The validate-prime uses the parsers and formatters on ng-model to update the validity of the model.

Now I want the invalid-add-class directive to add the class "error" when the model is invalid, and to remove it when it is valid. In other words, it should watch the $valid (or $invalid) property of the model controller. However, I can't figure out how to get this working. I tried:

link : function(scope, element, attrs, ctrl) {
    ctrl.$watch("$valid", function(newVal, oldVal) {
    //never fired
    });
}

Perhaps I could watch some variable on scope, but I don't know which variable to watch for.

So how can I be notified when the validity of a model changes?

3
if you can share your directive it will be nice! - Nimrod Shory
I don't remember what I exactly used this for, but CaioToOn's second solution is pretty much the entire link function already. Only the body of the second function should be filled with whatever action you want to when the validity changes. - Tiddo

3 Answers

63
votes

If you have a <form>, add a name to it (lets assume 'myForm') and a name to your input (lets assume myInput). You should be able to $watch this by:

scope.$watch('myForm.myInput.$valid', function(validity) {})

If you don't have a form, you can always watch a function. This way:

scope.$watch(function() { return ctrl.$valid; }, function(validity){});

You can read more about the form approach here.

12
votes

If you do not have a <form />you can easily get one:

In your directive definition:

require: '^form'

and then in your link function, the form is passed as the fourth parameter:

    link: function (scope, element, attr, ctrl) {

Now you don't have to hard-code the form or the input field to perform the $watch:

 scope.$watch(ctrl.$name + '.' + element.attr('name') + '.$valid', 
 function (validity) {}); 
7
votes

Our goal, in general, should be to make a directive work independently of any one form or input. How can we allow it to read the local $valid property without imperatively binding it to a single specific form & input name?

Just use require: 'ngModel' as one of the properties of your directive config. This will inject the local ngModel controller as the fourth argument to the link function, and you can place a $watch directly upon $valid without needing to couple the directive's implementation to any particular form or input.

require: 'ngModel',
link: function postLink(scope, element, attrs, controller) {
    scope.inputCtrl = controller;
    scope.$watch('inputCtrl.$valid', handlerFunc)
}

The handler should consistently fire on changes to $valid with that structure. See this Fiddle, where the input is validated for the pattern of a U.S. Zip-Code or Zip+4. You'll get an alert each time validity changes.

EDIT 3/21/14: This post previously got hung up on a delusion of mine, fixating on the wrong cause of an implementation problem. My fault. The example above removes that fixation. Also, added the fiddle, showing that this approach does in fact work, and always did, once you add quotes around the watch expression.