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

Commit 21b77ad

Browse files
committed
fix(form): preperly clean up when invalid widget is removed
Removing invalid widget sometimes resulted in improper cleanup of the form state.
1 parent 2f5dba4 commit 21b77ad

File tree

2 files changed

+66
-38
lines changed

2 files changed

+66
-38
lines changed

src/ng/directive/form.js

+28-33
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,43 @@ function FormController(element, attrs) {
7070
if (control.$name && form[control.$name] === control) {
7171
delete form[control.$name];
7272
}
73-
forEach(errors, cleanupControlErrors, control);
73+
forEach(errors, function(queue, validationToken) {
74+
form.$setValidity(validationToken, true, control);
75+
});
7476
};
7577

7678
form.$setValidity = function(validationToken, isValid, control) {
77-
if (isValid) {
78-
cleanupControlErrors(errors[validationToken], validationToken, control);
79+
var queue = errors[validationToken];
7980

80-
if (!invalidCount) {
81-
toggleValidCss(isValid);
82-
form.$valid = true;
83-
form.$invalid = false;
81+
if (isValid) {
82+
if (queue) {
83+
arrayRemove(queue, control);
84+
if (!queue.length) {
85+
invalidCount--;
86+
if (!invalidCount) {
87+
toggleValidCss(isValid);
88+
form.$valid = true;
89+
form.$invalid = false;
90+
}
91+
errors[validationToken] = false;
92+
toggleValidCss(true, validationToken);
93+
parentForm.$setValidity(validationToken, true, form);
94+
}
8495
}
96+
8597
} else {
8698
if (!invalidCount) {
8799
toggleValidCss(isValid);
88100
}
89-
addControlError(validationToken, control);
101+
if (queue) {
102+
if (includes(queue, control)) return;
103+
} else {
104+
errors[validationToken] = queue = [];
105+
invalidCount++;
106+
toggleValidCss(false, validationToken);
107+
parentForm.$setValidity(validationToken, false, form);
108+
}
109+
queue.push(control);
90110

91111
form.$valid = false;
92112
form.$invalid = true;
@@ -99,31 +119,6 @@ function FormController(element, attrs) {
99119
form.$pristine = false;
100120
};
101121

102-
function cleanupControlErrors(queue, validationToken, control) {
103-
if (queue) {
104-
control = control || this; // so that we can be used in forEach;
105-
arrayRemove(queue, control);
106-
if (!queue.length) {
107-
invalidCount--;
108-
errors[validationToken] = false;
109-
toggleValidCss(true, validationToken);
110-
parentForm.$setValidity(validationToken, true, form);
111-
}
112-
}
113-
}
114-
115-
function addControlError(validationToken, control) {
116-
var queue = errors[validationToken];
117-
if (queue) {
118-
if (includes(queue, control)) return;
119-
} else {
120-
errors[validationToken] = queue = [];
121-
invalidCount++;
122-
toggleValidCss(false, validationToken);
123-
parentForm.$setValidity(validationToken, false, form);
124-
}
125-
queue.push(control);
126-
}
127122
}
128123

129124

test/ng/directive/formSpec.js

+38-5
Original file line numberDiff line numberDiff line change
@@ -189,16 +189,16 @@ describe('form', function() {
189189

190190
it('should deregister a child form when its DOM is removed', function() {
191191
doc = jqLite(
192-
'<form name="parent">' +
193-
'<div class="ng-form" name="child">' +
194-
'<input ng:model="modelA" name="inputA" required>' +
195-
'</div>' +
192+
'<form name="parent">' +
193+
'<div class="ng-form" name="child">' +
194+
'<input ng:model="modelA" name="inputA" required>' +
195+
'</div>' +
196196
'</form>');
197197
$compile(doc)(scope);
198198
scope.$apply();
199199

200200
var parent = scope.parent,
201-
child = scope.child;
201+
child = scope.child;
202202

203203
expect(parent).toBeDefined();
204204
expect(child).toBeDefined();
@@ -211,6 +211,39 @@ describe('form', function() {
211211
});
212212

213213

214+
it('should deregister a input when its removed from DOM', function() {
215+
doc = jqLite(
216+
'<form name="parent">' +
217+
'<div class="ng-form" name="child">' +
218+
'<input ng:model="modelA" name="inputA" required>' +
219+
'</div>' +
220+
'</form>');
221+
$compile(doc)(scope);
222+
scope.$apply();
223+
224+
var parent = scope.parent,
225+
child = scope.child,
226+
input = child.inputA;
227+
228+
expect(parent).toBeDefined();
229+
expect(child).toBeDefined();
230+
expect(parent.$error.required).toEqual([child]);
231+
expect(child.$error.required).toEqual([input]);
232+
expect(doc.hasClass('ng-invalid')).toBe(true);
233+
expect(doc.hasClass('ng-invalid-required')).toBe(true);
234+
expect(doc.find('div').hasClass('ng-invalid')).toBe(true);
235+
expect(doc.find('div').hasClass('ng-invalid-required')).toBe(true);
236+
doc.find('input').remove(); //remove child
237+
238+
expect(parent.$error.required).toBe(false);
239+
expect(child.$error.required).toBe(false);
240+
expect(doc.hasClass('ng-valid')).toBe(true);
241+
expect(doc.hasClass('ng-valid-required')).toBe(true);
242+
expect(doc.find('div').hasClass('ng-valid')).toBe(true);
243+
expect(doc.find('div').hasClass('ng-valid-required')).toBe(true);
244+
});
245+
246+
214247
it('should chain nested forms in repeater', function() {
215248
doc = jqLite(
216249
'<ng:form name=parent>' +

0 commit comments

Comments
 (0)