Reset form to pristine state (AngularJS 1.0.x)

I think it's worth mentioning that in later versions of Angular (e.g. 1.1.5), you can call $setPristine on the form.

$scope.formName.$setPristine(true)

This will set all the form controls to pristine state as well.

FormController.$setPristine


Solution without a workaround

I came up with a solution which uses AngularJS without any workaround. The trick here is to use AngularJS ability to have more than one directive with the same name.

As others mentioned there is actually a pull request (https://github.com/angular/angular.js/pull/1127) which made it into the AngularJS 1.1.x branch which allows forms to be reset. The commit to this pull request alters the ngModel and form/ngForm directives (I would have liked to add a link but Stackoverflow doesn't want me to add more than two links).

We can now define our own ngModel and form/ngForm directives and extend them with the functionality provided in the pull request.

I have wrapped these directives in a AngularJS module named resettableForm. All you have to do is to include this module to your project and your AngularJS version 1.0.x behaves as if it was an Angular 1.1.x version in this regard.

''Once you update to 1.1.x you don't even have to update your code, just remove the module and you are done!''

This module also passes all tests added to the 1.1.x branch for the form reset functionality.

You can see the module working in an example in a jsFiddle (http://jsfiddle.net/jupiter/7jwZR/1/) I created.

Step 1: Include the resettableform module in your project

(function(angular) {

// Copied from AngluarJS
function indexOf(array, obj) {
  if (array.indexOf) return array.indexOf(obj);

  for ( var i = 0; i < array.length; i++) {
    if (obj === array[i]) return i;
  }
  return -1;
}

// Copied from AngularJS
function arrayRemove(array, value) {
  var index = indexOf(array, value);
  if (index >=0)
    array.splice(index, 1);
  return value;
}

// Copied from AngularJS
var PRISTINE_CLASS = 'ng-pristine';
var DIRTY_CLASS = 'ng-dirty';

var formDirectiveFactory = function(isNgForm) {
    return function() {
        var formDirective = {
            restrict: 'E',
            require: ['form'],
            compile: function() {
                return {
                    pre: function(scope, element, attrs, ctrls) {
                        var form = ctrls[0];
                        var $addControl = form.$addControl;
                        var $removeControl = form.$removeControl;
                        var controls = [];
                        form.$addControl = function(control) {
                            controls.push(control);
                            $addControl.apply(this, arguments);
                        }
                        form.$removeControl = function(control) {
                            arrayRemove(controls, control);
                            $removeControl.apply(this, arguments);
                        }
                        form.$setPristine = function() {
                            element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
                            form.$dirty = false;
                            form.$pristine = true;
                            angular.forEach(controls, function(control) {
                                control.$setPristine();
                            });
                        }
                    },
                };
            },
        };
        return isNgForm ? angular.extend(angular.copy(formDirective), {restrict: 'EAC'}) : formDirective;
    };
}
var ngFormDirective = formDirectiveFactory(true);
var formDirective = formDirectiveFactory();
angular.module('resettableForm', []).
    directive('ngForm', ngFormDirective).
    directive('form', formDirective).
    directive('ngModel', function() {
        return {
            require: ['ngModel'],
            link: function(scope, element, attrs, ctrls) {
                var control = ctrls[0];
                control.$setPristine = function() {
                    this.$dirty = false;
                    this.$pristine = true;
                    element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
                }
            },
        };
    });
})(angular);

Step 2: Provide a method on your controller which resets the model

Please be aware that you must reset the model when you reset the form. In your controller you can write:

var myApp = angular.module('myApp', ['resettableForm']);

function MyCtrl($scope) {
    $scope.reset = function() {
        $scope.form.$setPristine();
        $scope.model = '';
    };
}

Step 3: Include this method in your HTML template

<div ng-app="myApp">
<div ng-controller="MyCtrl">
<form name="form">
    <input name="requiredField" ng-model="model.requiredField" required/> (Required, but no other validators)
    <p ng-show="form.requiredField.$errror.required">Field is required</p>
    <button ng-click="reset()">Reset form</button>
</form>
<p>Pristine: {{form.$pristine}}</p>
</div>
</dvi>

EDIT... I'm removing my old answer, as it was not adequate.

I actually just ran into this issue myself and here was my solution: I made an extension method for angular. I did so by following a bit of what $scope.form.$setValidity() was doing (in reverse)...

Here's a plnkr demo of it in action

Here's the helper method I made. It's a hack, but it works:

angular.resetForm = function (scope, formName, defaults) {
    $('form[name=' + formName + '], form[name=' + formName + '] .ng-dirty').removeClass('ng-dirty').addClass('ng-pristine');
    var form = scope[formName];
    form.$dirty = false;
    form.$pristine = true;
    for(var field in form) {
      if(form[field].$pristine === false) {
        form[field].$pristine = true;
      }
      if(form[field].$dirty === true) {
        form[field].$dirty = false;
      }
    }
    for(var d in defaults) {
      scope[d] = defaults[d];
    }
};

Hopefully this is helpful to someone.