使用 angularjs 检测未保存的更改并提醒用户

IT技术 javascript angularjs angularjs-directive
2021-03-13 18:49:28

以下是到目前为止的代码

    <!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万一有一些未保存的数据,使用户可以决定是否继续?

6个回答

像这样的事情应该这样做:

<!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 应用程序中工作。

我想提出以下改进建议: $scope[element.attr('name')].$dirty 所以它适用于使用指令的任何通用形式
2021-04-22 18:49:28
注意:对于使用 Angular ui-router 的人,你应该使用 '$stateChangeStart' 而不是 '$locationChangeStart'。谢谢@暴雪
2021-04-24 18:49:28
很好的解决方案。简单高效!
2021-05-04 18:49:28
但这东西不适用于浏览器后退按钮。有什么技巧可以让它工作吗?
2021-05-06 18:49:28

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

确认退出指令

/**
 * @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,也会触发。
2021-05-01 18:49:28
试试这个: if (current.split("#")[1] != next.split("#")[1] && $scope.confirmOnExit()) {...我比较拆分数组的第二个字符串因为第一个哈希值就在 index.html 后面。要绝对确定 url 包含 anker,您必须进行一些更复杂的比较,也许使用正则表达式。
2021-05-10 18:49:28
只是一个快速的笔记。如果您使用 'reloadOnSearch' 来阻止对 $location.search 更改的导航,则 '$locationChangeStart' 事件仍会弹出对话框,即使导航会被阻止。在这种情况下,'$routeChangeStart' 事件似乎是更好的选择。
2021-05-16 18:49:28
忽略来自表单上提交按钮的点击的最佳方法是什么?
2021-05-16 18:49:28

安德斯的回答工作正常,但是,谁使用角UI路由器的人,你应该使用'$stateChangeStart'来代替'$locationChangeStart'

我已将此作为评论添加到他的回答中,谢谢@Blizzard
2021-05-04 18:49:28

我修改了@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> 

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

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

惊人的!但据我所知,它只能听表格。对?
2021-05-05 18:49:28
与 ui-router一起使用的其他指令github.com/facultymatt/angular-unsavedChanges
2021-05-12 18:49:28