Robert Koritnik
Robert Koritnik

Reputation: 105029

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).

Upvotes: 6

Views: 5549

Answers (2)

Khanh TO
Khanh TO

Reputation: 48972

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

Upvotes: 1

Stewie
Stewie

Reputation: 60406

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);
      }
    };
}]);

Upvotes: 3

Related Questions