Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular directive with scope.$watch to force validation of other fields

I've written a match-model Angular directive that I use for password/password-repeat process when users register in my application. Password repeat field has this particular attribute that validates this field against original password field.

My directive has scope.$watch for optimization purposes because I don't have to read related scope property value each time I validate my repeat password scope property but I rather just use cached value which changes when related scope property value changes (original password).

This is my directive:

.directive("matchModel", ["$timeout", function ($timeout) {
    return {
        require: "ngModel",
        link: function (scope, element, attributes, ngModelController) {

            var valueCache = null;

            // watch "match-model" model property value
            scope.$watch(attributes["matchModel"], function (newValue, oldValue) {
                valueCache = newValue;
                /*
                scope.$apply(); // error $digest in progress
                $timeout(function () { scope.$digest(); }); // no error, not working
                $timeout(function () { scope.$apply(); }); // no error, not working
                */
            });

            // add model validation parser
            ngModelController.$parsers.unshift(function (value) {
                ngModelController.$setValidity("match", value === valueCache);
                return value === valueCache ? value : undefined;
            });
        }
    };
}]);

My form consists of two fields (that are relevant for this question):

<input type="password" name="password" id="password" placeholder="password"
       class="validate" autocomplete="off"
       required
       ng-minlength="6"
       ng-model="data.password" />
<input type="password" name="passwordRepeat" id="passwordRepeat" placeholder="repeat password"
       class="validate" autocomplete="off"
       required
       ng-model="data.passwordRepeat"
       match-model="data.password" />

Requirements

  1. When user enters first password, field becomes valid when enough characters are entered - in above case that's 6 characters
  2. when user enters second password, field should become valid when data matches first password
  3. if user returns to first field and changes original password, second field should invalidate

How it currently works

1 and 2 work as expected, but 3 doesn't. That's why I wanted to add scope.$digest to propagate scope model changes to other fields. And scope.$watch is the right moment because it executes when that particular scope model property changes.

It seems that scope.$digest (or scope.$apply for that matter) doesn't validate model. Validation doesn't seem to be executed along with it.

Question

So how should I do something like scope.$validate or even better element.$validate so it would only validate my particular field instead of the whole model (resulting in invalid form in the UI).

like image 967
Robert Koritnik Avatar asked Jan 29 '14 10:01

Robert Koritnik


2 Answers

I would do it by explicitly validating the $viewValue inside the $watch:

PLUNKER

app.directive("matchModel", [
  function () {
    return {
      require: "ngModel",
      link: function (scope, element, attributes, ngModelController) {

        var valueCache = null;

        scope.$watch(attributes["matchModel"], function (newValue, oldValue) {
          valueCache = newValue;
          validate(ngModelController.$viewValue);
        });

        var validate = function (value) {
          ngModelController.$setValidity("match", value === valueCache);
          return value === valueCache ? value : undefined;
        };

        ngModelController.$parsers.unshift(validate);
      }
    };
}]);
like image 91
Stewie Avatar answered Nov 01 '22 08:11

Stewie


I would do it this way with only one place to check if they are equal:

app.directive("matchModel", function () {
    return {
        require: "ngModel",
        link: function (scope, element, attributes, ngModelController) {

            scope.$watch(function(){
               return scope.$eval(attributes["matchModel"]) == ngModelController.$viewValue; //only 1 place to check if they are equal
            }, function (newValue) {
                ngModelController.$setValidity("match", newValue);
            });

        }
    };
});

DEMO

like image 25
Khanh TO Avatar answered Nov 01 '22 09:11

Khanh TO