Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Fix broken ui-validate-watch for retroactive dependencies #472

Merged
merged 3 commits into from

5 participants

@ovmjm

In the ui-angular-watch implementation we should not re-validate against the $modelValue but the $viewValue of the field since :

  1. this is what angular naturally does ;
  2. most of the time the $modelValue is undefined since angular values it if and only if the $viewValue is valid first.

The problem can be reproduced on the demo page by filling the passwordConfirmation first, and then the password. The passwordConfirmation stays invalid which is not expected.

The attached pull request fixes the problem by triggering again the natural angular validation process by simulating a change in the $viewValue

ctrl.$setViewValue(ctrl.$viewValue);

For instance, with this fix, the following cyclic five fields dependency schema works like a charm

A -> B -> C -> D -> E -> A
@ProLoser
Owner

Since you are triggering the entire stack of validators there is no reason to have multiple watches, and since you have multiple watches there is no longer a reason to wrap the expression in a string and double evaluate it.

You should also update the tests and possibly add one for your patch

@ovmjm

String removed. Much simpler. Not backward compatible.
Tests updated.
Please ignore the commit 8c084ea: Fake version tag. Nothing to do with this issue.

@ovmjm

Any news about this issue ?

@inklesspen

I ran into this same issue today. Is there a temporary workaround while we wait for the pull request?

@ProLoser ProLoser merged commit b0d100b into angular-ui:master

1 check passed

Details default The Travis build passed
@ProLoser
Owner

The pull request IS the workaround.

@rintaun

This works great, thanks! One thing worth mentioning is that the model in the ui-validate-watch attribute must not be quoted as currently shown in the docs.

And one problem -- this method sets the field to dirty right off the bat, which can be problematic for styling.

@dmackerman

This isn't working for me in 1.2. I'm trying to verify that a specific field is less than another, and vice versa.

<input type="text" autocomplete="off" id="startDate" maxlength="4" name="startDate" 
  ng-model="education.startDate" 
  ui-validate="$value <= education.endDate"
  ui-validate-watch="education.endDate" required />

<input type="text" autocomplete="off" id="endDate" maxlength="4" name="endDate"
  ng-model="education.endDate" 
  ui-validate="$value >= education.startDate"
  ui-validate-watch="education.startDate" required />

Am I doing something wrong here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 3, 2013
  1. @ovmjm
  2. @ovmjm

    Fake tag version

    ovmjm authored
  3. @ovmjm

    Simplify ui-validate-watch syntax

    ovmjm authored
    See issue #472 for details.
This page is out of date. Refresh to see the latest.
View
4 component.json
@@ -2,7 +2,7 @@
"author": "AngularUI Team",
"name": "angular-ui",
"description": "AngularUI - Companion Suite for AngularJS",
- "version": "0.4.0",
+ "version": "0.4.1",
"homepage": "http://angular-ui.github.com",
"repository": {
"type": "git",
@@ -13,4 +13,4 @@
"jquery": ">= 1.8.0",
"angular": ">= 1.0.2"
}
-}
+}
View
47 modules/directives/validate/test/validateSpec.js
@@ -1,5 +1,5 @@
describe('uiValidate', function ($compile) {
- var scope, compileAndDigest;
+ var scope, compileAndDigest, changeInputValue;
var trueValidator = function () {
return true;
@@ -26,6 +26,10 @@ describe('uiValidate', function ($compile) {
return inputElm;
};
+ changeInputValue = function(elm, value) {
+ elm.val(value);
+ elm.trigger('input');
+ };
}));
describe('initial validation', function () {
@@ -105,9 +109,9 @@ describe('uiValidate', function ($compile) {
scope.validateWatch = validateWatch;
});
- it('should watch the string and refire the single validator', function () {
+ it('should watch the model and refire the single validator', function () {
scope.watchMe = false;
- compileAndDigest('<input name="input" ng-model="value" ui-validate="\'validateWatch(watchMe)\'" ui-validate-watch="\'watchMe\'">', scope);
+ compileAndDigest('<input name="input" ng-model="value" ui-validate="\'validateWatch(watchMe)\'" ui-validate-watch="watchMe">', scope);
expect(scope.form.input.$valid).toBe(false);
expect(scope.form.input.$error.validator).toBe(true);
scope.$apply('watchMe=true');
@@ -115,9 +119,9 @@ describe('uiValidate', function ($compile) {
expect(scope.form.input.$error.validator).toBe(false);
});
- it('should watch the string and refire all validators', function () {
+ it('should watch the model and refire all validators', function () {
scope.watchMe = false;
- compileAndDigest('<input name="input" ng-model="value" ui-validate="{foo:\'validateWatch(watchMe)\',bar:\'validateWatch(watchMe)\'}" ui-validate-watch="\'watchMe\'">', scope);
+ compileAndDigest('<input name="input" ng-model="value" ui-validate="{foo:\'validateWatch(watchMe)\',bar:\'validateWatch(watchMe)\'}" ui-validate-watch="watchMe">', scope);
expect(scope.form.input.$valid).toBe(false);
expect(scope.form.input.$error.foo).toBe(true);
expect(scope.form.input.$error.bar).toBe(true);
@@ -127,26 +131,25 @@ describe('uiValidate', function ($compile) {
expect(scope.form.input.$error.bar).toBe(false);
});
- it('should watch the all object attributes and each respective validator', function () {
- scope.watchFoo = false;
- scope.watchBar = false;
- compileAndDigest('<input name="input" ng-model="value" ui-validate="{foo:\'validateWatch(watchFoo)\',bar:\'validateWatch(watchBar)\'}" ui-validate-watch="{foo:\'watchFoo\',bar:\'watchBar\'}">', scope);
- expect(scope.form.input.$valid).toBe(false);
- expect(scope.form.input.$error.foo).toBe(true);
- expect(scope.form.input.$error.bar).toBe(true);
- scope.$apply('watchFoo=true');
+ it('should watch the model and refire the validation with the view $value', function () {
+ scope.value = "";
+ scope.watchMe = "bbb";
+ var input = compileAndDigest('<input name="input" ng-model="value" ui-validate="\'$value == watchMe\'" ui-validate-watch="watchMe">', scope);
+
+ // Initial state, form empty, invalid view value
expect(scope.form.input.$valid).toBe(false);
- expect(scope.form.input.$error.foo).toBe(false);
- expect(scope.form.input.$error.bar).toBe(true);
- scope.$apply('watchBar=true');
- scope.$apply('watchFoo=false');
+ expect(scope.form.input.$error.validator).toBe(true);
+ expect(scope.value).toBe(undefined); // Since the validator fails, the model is unset
+
+ changeInputValue(input, "aaa"); // The user types something invalid
expect(scope.form.input.$valid).toBe(false);
- expect(scope.form.input.$error.foo).toBe(true);
- expect(scope.form.input.$error.bar).toBe(false);
- scope.$apply('watchFoo=true');
+ expect(scope.form.input.$error.validator).toBe(true);
+ expect(scope.value).toBe(undefined); // Since the validator still fails, the model is still unset
+
+ scope.$apply('watchMe="aaa"'); // Now the watched model changes and matches the previously invalid view value
expect(scope.form.input.$valid).toBe(true);
- expect(scope.form.input.$error.foo).toBe(false);
- expect(scope.form.input.$error.bar).toBe(false);
+ expect(scope.form.input.$error.validator).toBe(false);
+ expect(scope.value).toBe("aaa"); // Since the validator succeeds, the model is set
});
});
View
19 modules/directives/validate/validate.js
@@ -20,7 +20,7 @@ angular.module('ui.directives').directive('uiValidate', function () {
restrict: 'A',
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
- var validateFn, watch, validators = {},
+ var validateFn, watches, validators = {},
validateExpr = scope.$eval(attrs.uiValidate);
if (!validateExpr) return;
@@ -46,20 +46,9 @@ angular.module('ui.directives').directive('uiValidate', function () {
// Support for ui-validate-watch
if (attrs.uiValidateWatch) {
- watch = scope.$eval(attrs.uiValidateWatch);
- if (angular.isString(watch)) {
- scope.$watch(watch, function(){
- angular.forEach(validators, function(validatorFn, key){
- validatorFn(ctrl.$modelValue);
- });
- });
- } else {
- angular.forEach(watch, function(expression, key){
- scope.$watch(expression, function(){
- validators[key](ctrl.$modelValue);
- });
- });
- }
+ scope.$watch(attrs.uiValidateWatch, function() {
+ ctrl.$setViewValue(ctrl.$viewValue);
+ });
}
}
};
Something went wrong with that request. Please try again.