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
- When user enters first password, field bees valid when enough characters are entered - in above case that's 6 characters
- when user enters second password, field should bee valid when data matches first password
- 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
- When user enters first password, field bees valid when enough characters are entered - in above case that's 6 characters
- when user enters second password, field should bee valid when data matches first password
- 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).
-
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
2 Answers
Reset to default 3I 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