Detecting unsaved changes and warning the user using angularjs

Below is the code

<!doctype html> <html ng-app> <head> <script src="http://code.angularjs.org/1.1.2/angular.min.js"></script> <script type="text/javascript"> function Ctrl($scope) { var initial = {text: 'initial value'}; $scope.myModel = angular.copy(initial); $scope.revert = function() { $scope.myModel = angular.copy(initial); $scope.myForm.$setPristine(); } } </script> </head> <body> <form name="myForm" ng-controller="Ctrl"> myModel.text: <input name="input" ng-model="myModel.text"> <p>myModel.text = {{myModel.text}}</p> <p>$pristine = {{myForm.$pristine}}</p> <p>$dirty = {{myForm.$dirty}}</p> <button ng-click="revert()">Set pristine</button> </form> </body> </html> 

How to warn browser close or url redirect in case there is incorrect data so that the user can decide whether to continue?

+35
javascript angularjs angularjs-directive
Feb 13 '13 at 11:45
source share
7 answers

Something like this should do this:

 <!doctype html> <html ng-app="myApp"> <head> <script src="http://code.angularjs.org/1.1.2/angular.min.js"></script> <script type="text/javascript"> function Ctrl($scope) { var initial = {text: 'initial value'}; $scope.myModel = angular.copy(initial); $scope.revert = function() { $scope.myModel = angular.copy(initial); $scope.myForm.$setPristine(); } } angular.module("myApp", []).directive('confirmOnExit', function() { return { link: function($scope, elem, attrs) { window.onbeforeunload = function(){ if ($scope.myForm.$dirty) { return "The form is dirty, do you want to stay on the page?"; } } $scope.$on('$locationChangeStart', function(event, next, current) { if ($scope.myForm.$dirty) { if(!confirm("The form is dirty, do you want to stay on the page?")) { event.preventDefault(); } } }); } }; }); </script> </head> <body> <form name="myForm" ng-controller="Ctrl" confirm-on-exit> myModel.text: <input name="input" ng-model="myModel.text"> <p>myModel.text = {{myModel.text}}</p> <p>$pristine = {{myForm.$pristine}}</p> <p>$dirty = {{myForm.$dirty}}</p> <button ng-click="revert()">Set pristine</button> </form> </body> </html> 

Please note that the listener for $ locationChangeStart does not start in this example, since AngularJS does not handle any routing in such a simple example, but it should work in a real Angular application.

+69
Feb 13 '13 at 12:25
source share

I expanded @Anders answer to clear the listeners (disable lists) when the directive is destroyed (for example: when changing the route) and added syntactic sugar to summarize usage.

confirmOnExit Directive :

 /** * @name confirmOnExit * * @description * Prompts user while he navigating away from the current route (or, as long as this directive * is not destroyed) if any unsaved form changes present. * * @element Attribute * @scope * @param confirmOnExit Scope function which will be called on window refresh/close or AngularS $route change to * decide whether to display the prompt or not. * @param confirmMessageWindow Custom message to display before browser refresh or closed. * @param confirmMessageRoute Custom message to display before navigating to other route. * @param confirmMessage Custom message to display when above specific message is not set. * * @example * Usage: * Example Controller: (using controllerAs syntax in this example) * * angular.module('AppModule', []).controller('pageCtrl', [function () { * this.isDirty = function () { * // do your logic and return 'true' to display the prompt, or 'false' otherwise. * return true; * }; * }]); * * Template: * * <div confirm-on-exit="pageCtrl.isDirty()" * confirm-message-window="All your changes will be lost." * confirm-message-route="All your changes will be lost. Are you sure you want to do this?"> * * @see * http://stackoverflow.com/a/28905954/340290 * * @author Manikanta G */ ngxDirectivesModule.directive('confirmOnExit', function() { return { scope: { confirmOnExit: '&', confirmMessageWindow: '@', confirmMessageRoute: '@', confirmMessage: '@' }, link: function($scope, elem, attrs) { window.onbeforeunload = function(){ if ($scope.confirmOnExit()) { return $scope.confirmMessageWindow || $scope.confirmMessage; } } var $locationChangeStartUnbind = $scope.$on('$locationChangeStart', function(event, next, current) { if ($scope.confirmOnExit()) { if(! confirm($scope.confirmMessageRoute || $scope.confirmMessage)) { event.preventDefault(); } } }); $scope.$on('$destroy', function() { window.onbeforeunload = null; $locationChangeStartUnbind(); }); } }; }); 

Usage: Example controller : (using the controllerAs syntax in this example)

 angular.module('AppModule', []).controller('pageCtrl', [function () { this.isDirty = function () { // do your logic and return 'true' to display the prompt, or 'false' otherwise. return true; }; }]); 

Template

 <div confirm-on-exit="pageCtrl.isDirty()" confirm-message-window="All your changes will be lost." confirm-message-route="All your changes will be lost. Are you sure you want to do this?"> 
+33
Mar 06 '15 at
source share

Anders answer works great. However, for people who use Angular ui-router, you should use '$stateChangeStart' instead of '$locationChangeStart' .

+16
Mar 09 '15 at 1:10
source share

I modified @Anders answer so that the directive does not contain the form name hardcoded:

  app.directive('confirmOnExit', function() { return { link: function($scope, elem, attrs, ctrl) { window.onbeforeunload = function(){ if ($scope[attrs["name"]].$dirty) { return "Your edits will be lost."; } } } }; }); 

Here is the html code for it:

 <form name="myForm" confirm-on-exit> 
+9
Nov 20 '15 at 21:37
source share

Maybe it will be useful for someone. https://github.com/umbrella-web/Angular-unsavedChanges

Using this service, you can listen to unsaved changes for any object in the field (not just forms)

+3
Dec 02 '13 at 13:14
source share

To use Anders Ekdahl's excellent answer with the Angular 1.5 component, enter $scope in the component controller:

 angular .module('myModule') .component('myComponent', { controller: ['$routeParams', '$scope', function MyController($routeParams, $scope) { var self = this; $scope.$on('$locationChangeStart', function (event, next, current) { if (self.productEdit.$dirty && !confirm('There are unsaved changes. Would you like to close the form?')) { event.preventDefault(); } }); } ] }); 
0
Oct 16 '16 at 7:23
source share

The accepted answer is great, but I had a problem getting the handle on my form controller correctly, because in some forms I use the form tag with the name attribute and in other cases I use the ng-form directive. Also, if you use typescript style functions that use a template such as this or vm for example, <form name='$ctrl.myForm'...

I am surprised that no one else mentioned this, but I decided to use the require property of the directive and let angular give me a link to the form controller itself.

I updated the accepted answer below to show my changes, note the require property and an additional parameter for the link function.

 angular.module("myApp", []).directive('confirmOnExit', function() { return { restrict: 'A', require: 'form', link: function($scope, elem, attrs, form) { window.onbeforeunload = function(){ if (form.$dirty) { return "The form is dirty, do you want to stay on the page?"; } } $scope.$on('$locationChangeStart', function(event, next, current) { if (form.$dirty) { if(!confirm("The form is dirty, do you want to stay on the page?")) { event.preventDefault(); } } }); } }; }); 

In doing so, I can guarantee that I have a good handle on the form controller, because angular will throw an error if it cannot find the form controller on the element.

You can also add modifiers like ^ and ? for example require='^form' to retrieve the ancestor form or require='?form' if the form is optional (this will not violate the directive, but you will need to verify that you have a handle to the actual form controller yourself),

0
Aug 02 '18 at 20:50
source share



All Articles