【问题标题】:Detect unsaved changes and alert user using angularjs使用 angularjs 检测未保存的更改并提醒用户
【发布时间】:2013-01-28 22:54:58
【问题描述】:

下面是目前的代码

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

如果有未保存的数据,如何在browser closeurl redirect上提醒,以便用户决定是否继续?

【问题讨论】:

    标签: javascript angularjs angularjs-directive


    【解决方案1】:

    应该这样做:

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

    请注意,$locationChangeStart 的侦听器在此示例中未触发,因为 AngularJS 在这样一个简单示例中不处理任何路由,但它应该在实际的 Angular 应用程序中工作。

    【讨论】:

    • 但是这个东西对浏览器后退按钮不起作用。任何黑客让它工作?
    • 很好的解决方案。简单高效!
    • 注意:对于使用 Angular ui-router 的人,您应该使用 '$stateChangeStart' 而不是 '$locationChangeStart'。谢谢@暴雪
    • 我想建议以下改进: $scope[element.attr('name')].$dirty 所以它适用于使用该指令的任何通用形式
    【解决方案2】:

    我扩展了@Anders 的答案,以便在指令被销毁时(例如:当路由更改时)清理侦听器(解除绑定列表),并添加了一些语法糖来概括用法。

    confirmOnExit 指令

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

    用法: 示例控制器(在此示例中使用 controllerAs 语法)

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

    模板

    <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?">
    

    【讨论】:

    • 这很好,但如果用户导航到同一页面上的anker,也会触发。
    • 试试这个: if (current.split("#")[1] != next.split("#")[1] && $scope.confirmOnExit()) {... I比较拆分数组的第二个字符串,因为第一个哈希就在 index.html 后面。要绝对确定 url 包含 anker,您必须进行一些更复杂的比较,也许使用正则表达式。
    • 只是一个简短的说明。如果您使用 'reloadOnSearch' 来阻止导航 $location.search 更改,'$locationChangeStart' 事件仍将弹出对话框,即使导航已被阻止。在这种情况下,'$routeChangeStart' 事件似乎是更好的选择。
    • 忽略来自表单上提交按钮的点击的最佳方法是什么?
    【解决方案3】:

    Anders 的回答很好用,但是对于使用 Angular ui-router 的人来说,你应该使用'$stateChangeStart' 而不是'$locationChangeStart'

    【讨论】:

    • 我已将此作为评论添加到他的回答中,谢谢@Blizzard
    【解决方案4】:

    我修改了@Anders 的答案,使指令不包含硬编码的表单名称:

        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.";
                        }
                    }
                }
            };
        });
    

    这是它的html代码:

    <form name="myForm" confirm-on-exit> 
    

    【讨论】:

      【解决方案5】:

      也许它会对某人有所帮助。 https://github.com/umbrella-web/Angular-unsavedChanges

      使用此服务,您可以监听范围内任何对象(不仅是表单)的未保存更改

      【讨论】:

      【解决方案6】:

      要将Anders Ekdahl 的出色答案用于Angular 1.5 组件,请在组件的控制器中注入$scope

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

      【讨论】:

        【解决方案7】:

        接受的答案很好,但是我在正确处理表单控制器时遇到了问题,因为某些表单我使用带有name 属性的form 标记,而在其他时候我使用ng-form指示。此外,如果您使用打字稿样式函数,使用 thisvm 类型模式,例如&lt;form name='$ctrl.myForm'...

        我很惊讶没有其他人提到这一点,但我的解决方法是利用指令的 require 属性并让 angular 给我一个对表单控制器本身的引用。

        我已经更新了下面接受的答案以显示我的更改,请注意链接函数的 require 属性和附加参数。

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

        有了这个,我可以保证我对表单控制器有很好的处理,因为如果 Angular 在元素上找不到表单控制器,它会抛出错误。

        您还可以添加诸如 ^? 之类的修饰符,例如 require='^form' 来拉取祖先表单或 require='?form' 如果表单是可选的(这不会破坏指令,但您需要自己检查是否拥有有效表单控制器的句柄)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-08-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-11-23
          • 2021-10-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多