Обнаружение несохраненных изменений и предупреждение пользователя с помощью angularjs
Ниже приведен код
<!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.
Ответ 2
Я расширил ответ @Anders, чтобы очистить слушателей (отключить списки), когда директива уничтожена (например: при изменении маршрута) и добавила синтаксический сахар для обобщения использования.
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();
});
}
};
});
Использование:
Контроллер примера: (используя синтаксис 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?">
Ответ 3
Ответ Anders отлично работает. Однако для людей, которые используют Angular ui-router, вы должны использовать '$stateChangeStart'
вместо '$locationChangeStart'
.
Ответ 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
Принятый ответ великолепен, однако у меня была проблема с правильным получением дескриптора на моем контроллере форм, поскольку в некоторых формах я использую тег form
с атрибутом name
а в других случаях я использую директиву ng-form
. Также, если вы используете функции стиля машинописного текста, использующие шаблон типа this
или vm
например, <form name='$ctrl.myForm'...
Я удивлен, что никто другой не упомянул об этом, но я решил использовать свойство require директивы и позволить angular дать мне ссылку на сам контроллер формы.
Я обновил принятый ответ ниже, чтобы показать мои изменения, отметить свойство require и дополнительный параметр для функции link.
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'
если форма является необязательной (это не нарушит директиву, но вам нужно будет проверить, что у вас есть дескриптор действительного контроллера формы самостоятельно),