Skip to content

Commit 61ac267

Browse files
committed
fix(form): make ngForm $pristine after nested control.$setPristine()
When calling $setPristine on the nested form or control, form becomes $pristine of all the nested controls are pristine Closes angular#13715
1 parent 1358719 commit 61ac267

File tree

4 files changed

+116
-6
lines changed

4 files changed

+116
-6
lines changed

Diff for: src/ng/directive/form.js

+32-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
var nullFormCtrl = {
66
$addControl: noop,
77
$$renameControl: nullFormRenameControl,
8+
$$updatePristine: noop,
89
$removeControl: noop,
910
$setValidity: noop,
1011
$setDirty: noop,
@@ -254,19 +255,46 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
254255
*
255256
* This method can be called to remove the 'ng-dirty' class and set the form to its pristine
256257
* state (ng-pristine class). This method will also propagate to all the controls contained
257-
* in this form.
258+
* in this form and to the parent form.
258259
*
259260
* Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
260261
* saving or resetting it.
261262
*/
262263
form.$setPristine = function() {
264+
form.$$setPristineSelf();
265+
forEach(controls, function(control) {
266+
control.$setPristine();
267+
});
268+
};
269+
270+
// Private API: Sets the form to its pristine state.
271+
// This method does not affect nested controls.
272+
form.$$setPristineSelf = function() {
263273
$animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
264274
form.$dirty = false;
265275
form.$pristine = true;
266276
form.$submitted = false;
267-
forEach(controls, function(control) {
268-
control.$setPristine();
269-
});
277+
form.$$parentForm.$$updatePristine();
278+
};
279+
280+
// Private API: update form pristine-ness
281+
form.$$updatePristine = function() {
282+
var isPristine = true,
283+
controlsLength = controls.length,
284+
i;
285+
286+
for (i = 0; i < controlsLength; i++) {
287+
if (!controls[i].$pristine) {
288+
isPristine = false;
289+
break;
290+
}
291+
}
292+
293+
if (isPristine) {
294+
// All the nested controls are already pristine.
295+
// Set pristine-ness only for the form itself.
296+
form.$$setPristineSelf();
297+
}
270298
};
271299

272300
/**

Diff for: src/ng/directive/ngModel.js

+1
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
383383
ctrl.$pristine = true;
384384
$animate.removeClass($element, DIRTY_CLASS);
385385
$animate.addClass($element, PRISTINE_CLASS);
386+
ctrl.$$parentForm.$$updatePristine();
386387
};
387388

388389
/**

Diff for: test/ng/directive/formSpec.js

+76-1
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,8 @@ describe('form', function() {
714714
expect(form.$error.maxlength[0].$name).toBe('childform');
715715

716716
inputController.$setPristine();
717-
expect(form.$dirty).toBe(true);
717+
// this assertion prevents to propagate prestine to the parent form
718+
// expect(form.$dirty).toBe(true);
718719

719720
form.$setPristine();
720721

@@ -1043,6 +1044,80 @@ describe('form', function() {
10431044
expect(nestedInputCtrl.$pristine).toBe(true);
10441045
expect(nestedInputCtrl.$dirty).toBe(false);
10451046
});
1047+
1048+
it('should propagate pristine-ness to the parent form', function() {
1049+
doc = $compile(
1050+
'<form name="parentForm">' +
1051+
'<div ng-form name="childForm"></div>' +
1052+
'</form>')(scope);
1053+
1054+
var parentForm = doc,
1055+
childForm = parentForm.find('div').eq(0),
1056+
childFormCtrl = scope.childForm;
1057+
1058+
childFormCtrl.$setDirty();
1059+
scope.$apply();
1060+
expect(parentForm).not.toBePristine();
1061+
1062+
childFormCtrl.$setPristine();
1063+
scope.$apply();
1064+
expect(childForm).toBePristine();
1065+
expect(parentForm).toBePristine();
1066+
});
1067+
1068+
it('should be pristine if all the nested controls are pristine', function() {
1069+
doc = $compile(
1070+
'<form name="form">' +
1071+
'<input ng-model="inputModel1" name="input1">' +
1072+
'<input ng-model="inputModel2" name="input2">' +
1073+
'</form>')(scope);
1074+
1075+
var form = doc,
1076+
formCtrl = scope.form,
1077+
input1 = form.find('input').eq(0),
1078+
input2 = form.find('input').eq(1),
1079+
inputCtrl1 = input1.controller('ngModel'),
1080+
inputCtrl2 = input2.controller('ngModel');
1081+
1082+
inputCtrl1.$setDirty();
1083+
inputCtrl2.$setDirty();
1084+
scope.$apply();
1085+
expect(form).not.toBePristine();
1086+
1087+
inputCtrl1.$setPristine();
1088+
scope.$apply();
1089+
expect(form).not.toBePristine();
1090+
1091+
inputCtrl2.$setPristine();
1092+
scope.$apply();
1093+
expect(form).toBePristine();
1094+
});
1095+
1096+
it('should be pristine if all the nested forms are pristine', function() {
1097+
doc = $compile(
1098+
'<form name="form">' +
1099+
'<div ng-form name="childForm1"></div>' +
1100+
'<div ng-form name="childForm2"></div>' +
1101+
'</form>')(scope);
1102+
1103+
var form = doc,
1104+
formCtrl = scope.form,
1105+
childFormCtrl1 = scope.childForm1,
1106+
childFormCtrl2 = scope.childForm2;
1107+
1108+
childFormCtrl1.$setDirty();
1109+
childFormCtrl2.$setDirty();
1110+
scope.$apply();
1111+
expect(form).not.toBePristine();
1112+
1113+
childFormCtrl1.$setPristine();
1114+
scope.$apply();
1115+
expect(form).not.toBePristine();
1116+
1117+
childFormCtrl2.$setPristine();
1118+
scope.$apply();
1119+
expect(form).toBePristine();
1120+
});
10461121
});
10471122

10481123
describe('$setUntouched', function() {

Diff for: test/ng/directive/ngModelSpec.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ describe('ngModel', function() {
1515
$$setPending: jasmine.createSpy('$$setPending'),
1616
$setValidity: jasmine.createSpy('$setValidity'),
1717
$setDirty: jasmine.createSpy('$setDirty'),
18-
$$clearControlValidity: noop
18+
$$clearControlValidity: noop,
19+
$$updatePristine: jasmine.createSpy('$$updatePristine')
1920
};
2021

2122
element = jqLite('<form><input></form>');
@@ -145,6 +146,11 @@ describe('ngModel', function() {
145146
expect(ctrl.$dirty).toBe(false);
146147
expect(ctrl.$pristine).toBe(true);
147148
});
149+
150+
it('should propagate pristine to the parent form', function() {
151+
ctrl.$setPristine();
152+
expect(parentFormCtrl.$$updatePristine).toHaveBeenCalledOnce();
153+
});
148154
});
149155

150156
describe('setDirty', function() {

0 commit comments

Comments
 (0)