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" />
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.
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 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
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