最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Angular directive with scope.$watch to force validation of other fields - Stack Overflow

programmeradmin1浏览0评论

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" autoplete="off"
       required
       ng-minlength="6"
       ng-model="data.password" />
<input type="password" name="passwordRepeat" id="passwordRepeat" placeholder="repeat password"
       class="validate" autoplete="off"
       required
       ng-model="data.passwordRepeat"
       match-model="data.password" />

Requirements

  1. When user enters first password, field bees valid when enough characters are entered - in above case that's 6 characters
  2. when user enters second password, field should bee 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).

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" autoplete="off"
       required
       ng-minlength="6"
       ng-model="data.password" />
<input type="password" name="passwordRepeat" id="passwordRepeat" placeholder="repeat password"
       class="validate" autoplete="off"
       required
       ng-model="data.passwordRepeat"
       match-model="data.password" />

Requirements

  1. When user enters first password, field bees valid when enough characters are entered - in above case that's 6 characters
  2. when user enters second password, field should bee 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).

Share Improve this question edited Jan 29, 2014 at 10:58 Robert Koritnik asked Jan 29, 2014 at 10:43 Robert KoritnikRobert Koritnik 105k56 gold badges286 silver badges413 bronze badges 2
  • Sounds like it might be simpler to use ng-validate and provide a controller function. – Davin Tryon Commented Jan 29, 2014 at 11:08
  • For those interested, I had a similar but slightly different case/question. This was my solution: stackoverflow./questions/34850124/… – Mathias Conradt Commented Jan 20, 2016 at 8:54
Add a ment  | 

2 Answers 2

Reset to default 3

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

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

发布评论

评论列表(0)

  1. 暂无评论