Prevent input from setting form $dirty angularjs

All we need is an easy explanation of the problem, so here it is.

I have an ng form on a page. Inside the form I have several controls which need to display a save dialog when the form is dirty, ie form.$dirty = true. However there are some navigation controls in the form I don’t want to dirty the form. Assume I can’t move the control out of the form.

see: http://plnkr.co/edit/bfig4B

How do I make the select box not dirty the form?

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Here’s a version of @acacia’s answer using a directive and not using $timeout. This will keep your controllers cleaner.

.directive('noDirtyCheck', function() {
  // Interacting with input elements having this directive won't cause the
  // form to be marked dirty.
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, elm, attrs, ctrl) {
      ctrl.$pristine = false;
    }
  }
});

Then use it in your form like so:

<input type="text" name="foo" ng-model="x.foo" no-dirty-check>

Method 2

I used @overthink’s solution, but ran into the problem mentioned by @dmitankin. However, I didn’t want to attach a handler to the focus event. So instead, I endeavored to override the $pristine property itself and force it to return false always. I ended up using Object.defineProperty which is not supported in IE8 and below. There are workarounds to do this in those legacy browsers, but I didn’t need them, so they are not part of my solution below:

(function () {
    angular
        .module("myapp")
        .directive("noDirtyCheck", noDirtyCheck);

    function noDirtyCheck() {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function (scope, elem, attrs, ctrl) {
                var alwaysFalse = {
                    get: function () { return false; },
                    set: function () { }
                };
                Object.defineProperty(ctrl, '$pristine', alwaysFalse);
                Object.defineProperty(ctrl, '$dirty', alwaysFalse);
            }
        };
    }
})();

I am also overriding $dirty so it can’t be set as dirty either.

Method 3

Setting the $pristine property to false, only when initializing, works until you call $setPristine() on the form. Then your control has its $pristine back to true and changing the input’s value would make your form dirty.
To avoid that, set the $pristine on focus:

link: function(scope, elm, attrs, ctrl) {
    elm.focus(function () {
        ctrl.$pristine = false;
    });
}

Method 4

Angular only sets the form dirty if the control is pristine. So the trick here is to set $pristine on the control to false. You can do it in a timeout in the controller.

see: http://plnkr.co/edit/by3qTM

Method 5

This is my final answer. Basically angular internally calls the $setDirty() function of the NgModelController when the input is interacted with, so just override that!

app.directive('noDirtyCheck', function() {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: postLink
  };
  function postLink(scope, iElem, iAttrs, ngModelCtrl) {
    ngModelCtrl.$setDirty = angular.noop;
  }
})

Method 6

A variation on @overthink’s answer with some additional validation, and inline bracket notation to protect against minification.

"use strict";

angular.module("lantern").directive("noDirtyCheck", [function () {
    return {
        restrict: "A",
        require: "ngModel",
        link: function (scope, elem, attrs, ngModelCtrl) {
            if (!ngModelCtrl) {
                return;
            }

            var clean = (ngModelCtrl.$pristine && !ngModelCtrl.$dirty);

            if (clean) {
                ngModelCtrl.$pristine = false;
                ngModelCtrl.$dirty = true;
            }
        }
    };
}]);

Method 7

I ran into some problems with that implementation, so here is mine (more complex):

app.directive('noDirtyCheck', [function () {
        // Interacting with input elements having this directive won't cause the
        // form to be marked dirty.
        // http://stackoverflow.com/questions/17089090/prevent-input-from-setting-form-dirty-angularjs
        return {
            restrict: 'A',           
            require: ['^form', '^ngModel'],

            link: function (scope, element, attrs, controllers) {
                var form = controllers[0];
                
                var currentControl = controllers[1];

                var formDirtyState = false;

                var manualFocus = false;

                element.bind('focus',function () {
                    manualFocus = true;
                    if (form) {                        
                        window.console && console.log('Saving current form ' + form.$name + ' dirty status: ' + form.$dirty);
                        formDirtyState = form.$dirty; // save form's dirty state
                    }
                 });
                
                element.bind('blur', function () {
                    if (currentControl) {
                        window.console && console.log('Resetting current control (' + currentControl.$name + ') dirty status to false (called from blur)');
                        currentControl.$dirty = false; // Remove dirty state but keep the value
                        if (!formDirtyState && form && manualFocus) {
                            window.console && console.log('Resetting ' + form.$name + ' form pristine state...');
                            form.$setPristine();
                        }
                        manualFocus = false;
                        //          scope.$apply();
                    }
                });
            }
        };
    }]);

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply