Detect unsaved changes and alert user using angularjs

前端 未结 7 1622
执念已碎
执念已碎 2020-11-29 18:50

Below is the code so far

    


    

        
相关标签:
7条回答
  • 2020-11-29 19:32

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

        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> 
    
    0 讨论(0)
  • 2020-11-29 19:37

    To use Anders Ekdahl's excellent answer with an Angular 1.5 component, inject the $scope in the component's 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 讨论(0)
  • 2020-11-29 19:40

    I've extended the @Anders answer to clean up listeners (unbind listers) when directive is destroyed (ex: when route changes), and added some syntactic sugar to generalise the 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 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?">
    
    0 讨论(0)
  • 2020-11-29 19:46

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

    Using this service you can listen unsaved changes for any object in the scope (not only the form)

    0 讨论(0)
  • 2020-11-29 19:46

    The accepted answer is great, however I had an issue with properly getting a handle on my form controller consistently since some forms I use the form tag with the name attribute and at other times i use the ng-form directive. Also, if you're using typescript style functions utilizing the this or vm type pattern e.g. <form name='$ctrl.myForm'...

    I'm surprised no one else has mentioned this, but my fix was to utilize the require property of the directive and let angular give me a reference to the form controller itself.

    I've updated the accepted answer below to show my changes, notice the require property and the additional parameter to 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();
                            }
                        }
                    });
                }
            };
        });
    

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

    You can also add modifiers like ^ and ? such as require='^form' to pull an ancestor form or require='?form' if the form is optional (this wont break the directive, but you will need to check that you have a handle on a valid form controller yourself).

    0 讨论(0)
  • 2020-11-29 19:53

    Something like this should do it:

    <!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>
    

    Note that the listener for $locationChangeStart isn't triggered in this example since AngularJS doesn't handle any routing in such a simple example, but it should work in an actual Angular application.

    0 讨论(0)
提交回复
热议问题