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

fix(form): make ngForm $pristine after nested control.$setPristine() (counter version) #13773

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions src/ng/directive/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
var nullFormCtrl = {
$addControl: noop,
$$renameControl: nullFormRenameControl,
$$updatePristine: noop,
$removeControl: noop,
$setValidity: noop,
$setDirty: noop,
Expand Down Expand Up @@ -254,19 +255,46 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
*
* This method can be called to remove the 'ng-dirty' class and set the form to its pristine
* state (ng-pristine class). This method will also propagate to all the controls contained
* in this form.
* in this form and to the parent form.
*
* Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
* saving or resetting it.
*/
form.$setPristine = function() {
form.$$setPristineSelf();
forEach(controls, function(control) {
control.$setPristine();
});
};

// Private API: Sets the form to its pristine state.
// This method does not affect nested controls.
form.$$setPristineSelf = function() {
$animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
form.$dirty = false;
form.$pristine = true;
form.$submitted = false;
forEach(controls, function(control) {
control.$setPristine();
});
form.$$parentForm.$$updatePristine();
};

// Private API: update form pristine-ness
form.$$updatePristine = function() {
var isPristine = true,
controlsLength = controls.length,
i;

for (i = 0; i < controlsLength; i++) {
if (!controls[i].$pristine) {
isPristine = false;
break;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicking: The above lines can be "summarized" as

var isPristine = controls.every(function(control) { return control.$pristine; });


if (isPristine) {
// All the nested controls are already pristine.
// Set pristine-ness only for the form itself.
form.$$setPristineSelf();
}
};

/**
Expand Down
1 change: 1 addition & 0 deletions src/ng/directive/ngModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
ctrl.$pristine = true;
$animate.removeClass($element, DIRTY_CLASS);
$animate.addClass($element, PRISTINE_CLASS);
ctrl.$$parentForm.$$updatePristine();
};

/**
Expand Down
77 changes: 76 additions & 1 deletion test/ng/directive/formSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,8 @@ describe('form', function() {
expect(form.$error.maxlength[0].$name).toBe('childform');

inputController.$setPristine();
expect(form.$dirty).toBe(true);
// this assertion prevents to propagate prestine to the parent form
// expect(form.$dirty).toBe(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should change this to expect(form.$dirty).toBe(false); and remove the subsequent call to form.$setPristine().


form.$setPristine();

Expand Down Expand Up @@ -1043,6 +1044,80 @@ describe('form', function() {
expect(nestedInputCtrl.$pristine).toBe(true);
expect(nestedInputCtrl.$dirty).toBe(false);
});

it('should propagate pristine-ness to the parent form', function() {
doc = $compile(
'<form name="parentForm">' +
'<div ng-form name="childForm"></div>' +
'</form>')(scope);

var parentForm = doc,
childForm = parentForm.find('div').eq(0),
childFormCtrl = scope.childForm;

childFormCtrl.$setDirty();
scope.$apply();
expect(parentForm).not.toBePristine();

childFormCtrl.$setPristine();
scope.$apply();
expect(childForm).toBePristine();
expect(parentForm).toBePristine();
});

it('should be pristine if all the nested controls are pristine', function() {
doc = $compile(
'<form name="form">' +
'<input ng-model="inputModel1" name="input1">' +
'<input ng-model="inputModel2" name="input2">' +
'</form>')(scope);

var form = doc,
formCtrl = scope.form,
input1 = form.find('input').eq(0),
input2 = form.find('input').eq(1),
inputCtrl1 = input1.controller('ngModel'),
inputCtrl2 = input2.controller('ngModel');

inputCtrl1.$setDirty();
inputCtrl2.$setDirty();
scope.$apply();
expect(form).not.toBePristine();

inputCtrl1.$setPristine();
scope.$apply();
expect(form).not.toBePristine();

inputCtrl2.$setPristine();
scope.$apply();
expect(form).toBePristine();
});

it('should be pristine if all the nested forms are pristine', function() {
doc = $compile(
'<form name="form">' +
'<div ng-form name="childForm1"></div>' +
'<div ng-form name="childForm2"></div>' +
'</form>')(scope);

var form = doc,
formCtrl = scope.form,
childFormCtrl1 = scope.childForm1,
childFormCtrl2 = scope.childForm2;

childFormCtrl1.$setDirty();
childFormCtrl2.$setDirty();
scope.$apply();
expect(form).not.toBePristine();

childFormCtrl1.$setPristine();
scope.$apply();
expect(form).not.toBePristine();

childFormCtrl2.$setPristine();
scope.$apply();
expect(form).toBePristine();
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a test to make sure a change propagates from a control through it's parent form to the outer-most form.

});

describe('$setUntouched', function() {
Expand Down
8 changes: 7 additions & 1 deletion test/ng/directive/ngModelSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ describe('ngModel', function() {
$$setPending: jasmine.createSpy('$$setPending'),
$setValidity: jasmine.createSpy('$setValidity'),
$setDirty: jasmine.createSpy('$setDirty'),
$$clearControlValidity: noop
$$clearControlValidity: noop,
$$updatePristine: jasmine.createSpy('$$updatePristine')
};

element = jqLite('<form><input></form>');
Expand Down Expand Up @@ -145,6 +146,11 @@ describe('ngModel', function() {
expect(ctrl.$dirty).toBe(false);
expect(ctrl.$pristine).toBe(true);
});

it('should propagate pristine to the parent form', function() {
ctrl.$setPristine();
expect(parentFormCtrl.$$updatePristine).toHaveBeenCalledOnce();
});
});

describe('setDirty', function() {
Expand Down