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

fix(form): make ngForm $pristine after nested control.$setPristine() #13772

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
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 @@ -235,19 +236,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();
});
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;
}
}

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

/**
Expand Down
3 changes: 3 additions & 0 deletions src/ng/directive/ngModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,12 +364,15 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* This method can be called to remove the `ng-dirty` class and set the control to its pristine
* state (`ng-pristine` class). A model is considered to be pristine when the control
* has not been changed from when first compiled.
*
* This method will also propagate to parent forms.
*/
this.$setPristine = function() {
ctrl.$dirty = false;
ctrl.$pristine = true;
$animate.removeClass($element, DIRTY_CLASS);
$animate.addClass($element, PRISTINE_CLASS);
parentForm.$$updatePristine();
};

/**
Expand Down
74 changes: 74 additions & 0 deletions test/ng/directive/formSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,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();
});
});

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

Expand Down Expand Up @@ -133,7 +134,6 @@ describe('ngModel', function() {
});

describe('setPristine', function() {

it('should set control to its pristine state', function() {
ctrl.$setViewValue('edit');
expect(ctrl.$dirty).toBe(true);
Expand All @@ -143,6 +143,11 @@ describe('ngModel', function() {
expect(ctrl.$dirty).toBe(false);
expect(ctrl.$pristine).toBe(true);
});

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

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