Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

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

Merged
merged 3 commits into from

5 participants

Ov Dean Sofer Jon Rosebaugh Matthew Lanigan Dave Ackerman
Ov

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
Dean Sofer
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

Ov

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

Ov

Any news about this issue ?

Jon Rosebaugh

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

Dean Sofer ProLoser merged commit b0d100b into from
Dean Sofer
Owner

The pull request IS the workaround.

Matthew Lanigan

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.

Dave Ackerman

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. Ov
  2. Ov

    Fake tag version

    ovmjm authored
  3. Ov

    Simplify ui-validate-watch syntax

    ovmjm authored
    See issue #472 for details.
This page is out of date. Refresh to see the latest.
4 component.json
View
@@ -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"
}
-}
+}
47 modules/directives/validate/test/validateSpec.js
View
@@ -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
});
});
19 modules/directives/validate/validate.js
View
@@ -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.