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.
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.
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;
};
}
};
});
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);
});
}
};
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With