Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angularjs setValidity causing modelValue to not update

I'm having some basic trouble with a form. Here's what I did.

I snagged this cool looking directive from here: https://github.com/TheSharpieOne/angular-input-match

It looks like this:

directive('match', function () {
    return {
      require: 'ngModel',
      restrict: 'A',
      scope: {
        match: '='
      },
      link: function(scope, elem, attrs, ngModel) {
        scope.$watch(function() {
          return (ngModel.$pristine && angular.isUndefined(ngModel.$modelValue)) || scope.match === ngModel.$viewValue;
        }, function(currentValue, previousValue) {
          ngModel.$setValidity('match', currentValue);
        });
      }
    };
  });

Essentially, this directive watches the element it is attached to's model value, and compares it to the model value in the match attribute.

So...for example, below we're watching to see if both passwords match:

Password: <input ng-model="password" type="password" />
Confirm: <input ng-model="passwordConfirm" type="password" match="password" />

The directive seems to be working, in that it sets ng-valid-match and ng-invalid-match appropriately.

However, once it is set to invalid, the passwordConfirm model never gets updated again. I've done a ton of console.loggin, looking at ngModel in the directive, and here is what it looks like when both passwords match:

Constructor {$viewValue: "asdf", $modelValue: undefined, $validators: Object, $parsers: Array[0], $formatters: Array[0]…}
$$debounceViewValueCommit: function (trigger, revalidate) {
$$invalidModelValue: "asdf"
$$lastCommittedViewValue: "asdf"
$$runValidators: function (modelValue, viewValue) {
$$validityState: ValidityState
$$writeModelToScope: function () {
$commitViewValue: function (revalidate) {
$dirty: true
$error: Object
$formatters: Array[0]
$invalid: false
$isEmpty: function (value) {
$modelValue: undefined
$name: "passwordConfirmation"
$parsers: Array[0]
$pristine: false
$render: function () {
$rollbackViewValue: function () {
$setPristine: function () {
$setTouched: function () {
$setUntouched: function () {
$setValidity: function (validationErrorKey, isValid) {
$setViewValue: function (value, trigger, revalidate) {
$touched: true
$untouched: false
$valid: true
$validate: function () {
$validators: Object
$viewChangeListeners: Array[0]
$viewValue: "asdf"
__proto__: Object

Note that the $viewValue is correct, but the $modelValue is listed as undefined and $invalidModelValue still has a value.

Here's what the html looks like, again when both passwords match:

<input type="password" class="form-control ng-isolate-scope ng-dirty ng-valid-required ng-valid ng-valid-match ng-touched" id="passwordConfirmation" name="passwordConfirmation" placeholder="Confirm your password" ng-model="passwordConfirmation" required="" match="password" style="">

Am I missing something somewhere? I've been running in circles for hours.

like image 582
CTC Avatar asked Jul 11 '14 07:07

CTC


3 Answers

In a recent update, a change was made to the way the $modelValue is populated based on the validity of the field. If the field is invalid, the $modelValue will be set to undefined and a new attribute, $$invalidModelValue will be populated with the value.

As a solution to work with 1.2.* and 1.3.* I have come up with this:

 .directive('match', function () {
    return {
        require: 'ngModel',
        restrict: 'A',
        scope: {
            match: '='
        },
        link: function(scope, elem, attrs, ctrl) {
            scope.$watch(function() {
                modelValue = ctrl.$modelValue || ctrl.$$invalidModelValue;
                return (ctrl.$pristine && angular.isUndefined(modelValue)) || scope.match === modelValue;
            }, function(currentValue) {
                ctrl.$setValidity('match', currentValue);
            });
        }
    };
});

Plunkr

While this solution works with both version, 1.3.* has the new $validators pipeline which is recommended for the new version.

like image 51
TheSharpieOne Avatar answered Nov 02 '22 09:11

TheSharpieOne


It looks like maybe using $setValidity is not the way to go here. I found this question that proposes a different solution, using $validators and $validate(), and this is working for me great. The new code looks like this:

directive('match', function () {
  return {
    require: 'ngModel',
    restrict: 'A',
    scope: {
      match: '='
    },
    link: function(scope, elem, attrs, ngModel) {
      scope.$watch('match', function(pass){
        ngModel.$validate();
      });
      ngModel.$validators.match = function(modelValue, viewValue){
        var value = modelValue || viewValue;
        var match = scope.match;
        return value === match;
      };
    }
  };
});
like image 42
CTC Avatar answered Nov 02 '22 09:11

CTC


Might be related to the fact that you are using

scope: { 
     match : "=" 
}

That create an isolated scope for your directive and doesn't herite from the parent scope where your ngModel is.

I suggest trying to remove that scope part of your directive and access it from attributes instead.

It will become something like :

directive('match', function () {
    return {
      require: 'ngModel',
      restrict: 'A',
      link: function(scope, elem, attrs, ngModel) {
        scope.match = attrs.match;
        scope.$watch(function() {
          return (ngModel.$pristine && angular.isUndefined(ngModel.$modelValue)) || scope.match === ngModel.$viewValue;
        }, function(currentValue, previousValue) {
          ngModel.$setValidity('match', currentValue);
        });
      }
    };
  });
like image 22
Wilfart Benjamin Avatar answered Nov 02 '22 09:11

Wilfart Benjamin