Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 4f4af0c

Browse files
committed
refactor(ngModelController,formController): centralize and simplify logic
The previous logic for async validation in `ngModelController` and `formController` was not maintainable: - control logic is in multiple parts, e.g. `ctrl.$setValidity` waits for end of promises and continuous the control flow for async validation - logic for updating the flags `ctrl.$error`, `ctrl.$pending`, `ctrl.$valid` is super complicated, especially in `formController` This refactoring makes the following changes: - simplify async validation: centralize control logic into one method in `ngModelController`: * remove counters `invalidCount` and `pendingCount` * use a flag `currentValidationRunId` to separate async validator runs from each other * use `$q.all` to determine when all async validators are done - centralize way how `ctrl.$modelValue` and `ctrl.$invalidModelValue` is updated - simplify `ngModelController/formCtrl.$setValidity` and merge `$$setPending/$$clearControlValidity/$$clearValidity/$$clearPending` into one method, that is used by `ngModelController` AND `formController` * remove diff calculation, always calculate the correct state anew, only cache the css classes that have been set to not trigger too many css animations. * remove fields from `ctrl.$error` that are valid and add `ctrl.$success`: allows to correctly separate states for valid, invalid, skipped and pending, especially transitively across parent forms. - fix bug in `ngModelController`: * only read out `input.validity.badInput`, but not `input.validity.typeMismatch`, to determine parser error: We still want our `email` validator to run event when the model is validated. - fix bugs in tests that were found as the logic is now consistent between `ngModelController` and `formController` BREAKING CHANGE: - `ctrl.$error` does no more contain entries for validators that were successful. Instead, they are now saved in `ctrl.$success`. - `ctrl.$setValidity` now differentiates between `true`, `false`, `undefined` and `null`, instead of previously only truthy vs falsy.
1 parent e322cd9 commit 4f4af0c

File tree

4 files changed

+301
-308
lines changed

4 files changed

+301
-308
lines changed

src/ng/directive/form.js

+35-99
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

3-
/* global -nullFormCtrl, -SUBMITTED_CLASS */
3+
/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true
4+
*/
45
var nullFormCtrl = {
56
$addControl: noop,
67
$removeControl: noop,
@@ -55,12 +56,12 @@ FormController.$inject = ['$element', '$attrs', '$scope', '$animate'];
5556
function FormController(element, attrs, $scope, $animate) {
5657
var form = this,
5758
parentForm = element.parent().controller('form') || nullFormCtrl,
58-
invalidCount = 0, // used to easily determine if we are valid
59-
pendingCount = 0,
60-
controls = [],
61-
errors = form.$error = {};
59+
controls = [];
6260

6361
// init state
62+
form.$error = {};
63+
form.$success = {};
64+
form.$pending = undefined;
6465
form.$name = attrs.name || attrs.ngForm;
6566
form.$dirty = false;
6667
form.$pristine = true;
@@ -72,14 +73,6 @@ function FormController(element, attrs, $scope, $animate) {
7273

7374
// Setup initial state of the control
7475
element.addClass(PRISTINE_CLASS);
75-
toggleValidCss(true);
76-
77-
// convenience method for easy toggling of classes
78-
function toggleValidCss(isValid, validationErrorKey) {
79-
validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
80-
$animate.removeClass(element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey);
81-
$animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
82-
}
8376

8477
/**
8578
* @ngdoc method
@@ -148,34 +141,16 @@ function FormController(element, attrs, $scope, $animate) {
148141
if (control.$name && form[control.$name] === control) {
149142
delete form[control.$name];
150143
}
144+
forEach(form.$pending, function(value, name) {
145+
form.$setValidity(name, null, control);
146+
});
147+
forEach(form.$error, function(value, name) {
148+
form.$setValidity(name, null, control);
149+
});
151150

152-
form.$$clearControlValidity(control);
153151
arrayRemove(controls, control);
154152
};
155153

156-
form.$$clearControlValidity = function(control) {
157-
forEach(form.$pending, clear);
158-
forEach(errors, clear);
159-
160-
function clear(queue, validationToken) {
161-
form.$setValidity(validationToken, true, control);
162-
}
163-
};
164-
165-
form.$$setPending = function(validationToken, control) {
166-
var pending = form.$pending && form.$pending[validationToken];
167-
168-
if (!pending || !includes(pending, control)) {
169-
pendingCount++;
170-
form.$valid = form.$invalid = undefined;
171-
form.$pending = form.$pending || {};
172-
if (!pending) {
173-
pending = form.$pending[validationToken] = [];
174-
}
175-
pending.push(control);
176-
parentForm.$$setPending(validationToken, form);
177-
}
178-
};
179154

180155
/**
181156
* @ngdoc method
@@ -186,72 +161,33 @@ function FormController(element, attrs, $scope, $animate) {
186161
*
187162
* This method will also propagate to parent forms.
188163
*/
189-
form.$setValidity = function(validationToken, isValid, control) {
190-
var queue = errors[validationToken];
191-
var pendingChange, pending = form.$pending && form.$pending[validationToken];
192-
193-
if (pending) {
194-
pendingChange = pending.indexOf(control) >= 0;
195-
if (pendingChange) {
196-
arrayRemove(pending, control);
197-
pendingCount--;
198-
199-
if (pending.length === 0) {
200-
delete form.$pending[validationToken];
201-
}
202-
}
203-
}
204-
205-
var pendingNoMore = form.$pending && pendingCount === 0;
206-
if (pendingNoMore) {
207-
form.$pending = undefined;
208-
}
209-
210-
if (isValid) {
211-
if (queue || pendingChange) {
212-
if (queue) {
213-
arrayRemove(queue, control);
214-
}
215-
if (!queue || !queue.length) {
216-
if (errors[validationToken]) {
217-
invalidCount--;
218-
}
219-
if (!invalidCount) {
220-
if (!form.$pending) {
221-
toggleValidCss(isValid);
222-
form.$valid = true;
223-
form.$invalid = false;
224-
}
225-
} else if(pendingNoMore) {
226-
toggleValidCss(false);
227-
form.$valid = false;
228-
form.$invalid = true;
229-
}
230-
errors[validationToken] = false;
231-
toggleValidCss(true, validationToken);
232-
parentForm.$setValidity(validationToken, true, form);
164+
addSetValidityMethod({
165+
ctrl: this,
166+
$element: element,
167+
set: function(object, property, control) {
168+
var list = object[property];
169+
if (!list) {
170+
object[property] = [control];
171+
} else {
172+
var index = list.indexOf(control);
173+
if (index === -1) {
174+
list.push(control);
233175
}
234176
}
235-
} else {
236-
if (!form.$pending) {
237-
form.$valid = false;
238-
form.$invalid = true;
177+
},
178+
unset: function(object, property, control) {
179+
var list = object[property];
180+
if (!list) {
181+
return;
239182
}
240-
241-
if (!invalidCount) {
242-
toggleValidCss(isValid);
183+
arrayRemove(list, control);
184+
if (list.length === 0) {
185+
delete object[property];
243186
}
244-
if (queue) {
245-
if (includes(queue, control)) return;
246-
} else {
247-
errors[validationToken] = queue = [];
248-
invalidCount++;
249-
toggleValidCss(false, validationToken);
250-
parentForm.$setValidity(validationToken, false, form);
251-
}
252-
queue.push(control);
253-
}
254-
};
187+
},
188+
parentForm: parentForm,
189+
$animate: $animate
190+
});
255191

256192
/**
257193
* @ngdoc method

0 commit comments

Comments
 (0)