Skip to content

Commit

Permalink
fix(form): make ngForm $pristine after nested control.$setPristine()
Browse files Browse the repository at this point in the history
When calling $setPristine on the nested form or control,
form becomes $pristine of all the nested controls are pristine

Closes angular#13715
  • Loading branch information
linoleum-js committed Jan 15, 2016
1 parent 1358719 commit 61ac267
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 6 deletions.
36 changes: 32 additions & 4 deletions src/ng/directive/form.js
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;
}
}

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
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
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);

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();
});
});

describe('$setUntouched', function() {
Expand Down
8 changes: 7 additions & 1 deletion test/ng/directive/ngModelSpec.js
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

0 comments on commit 61ac267

Please sign in to comment.