使用 angularjs 检测未保存的更改并提醒用户
Posted
技术标签:
【中文标题】使用 angularjs 检测未保存的更改并提醒用户【英文标题】:Detect unsaved changes and alert user using 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 close
或url redirect
上提醒,以便用户决定是否继续?
【问题讨论】:
【参考方案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://***.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
使用此服务,您可以监听范围内任何对象(不仅是表单)的未保存更改
【讨论】:
其他适用于 ui-router github.com/facultymatt/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
指示。此外,如果您使用打字稿样式函数,使用 this
或 vm
类型模式,例如<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'
如果表单是可选的(这不会破坏指令,但您需要自己检查是否拥有有效表单控制器的句柄)。
【讨论】:
以上是关于使用 angularjs 检测未保存的更改并提醒用户的主要内容,如果未能解决你的问题,请参考以下文章
使用 Web API 和 AngularJS 保存表单数据(选择列表)