Reputation: 8638
Is it possible to listen for route changes AND onwindowunload both to confirm page leave without saving changes?
Use cases:
If the user clicks 'cancel' stop the page / route change.
I've seen a few different examples but none worked quite right.
Upvotes: 16
Views: 31189
Reputation: 27023
For Angular ES6 Users
export default class myRouteController {
constructor($scope, $window) {
this.$scope = $scope;
this.$window = $window;
this.$scope.$on('$stateChangeStart', (evt) => {
if(this.form.$dirty && !$window.confirm('Are you sure you want to leave this page?')) {
evt.preventDefault();
}
});
}
}
Upvotes: 0
Reputation: 955
You'll find the $locationChangeStart
is an event you can listen for to detect for a route change.
Try something like
$rootScope.$on('$locationChangeStart', function (event, next, current) {
/* do some verification */
});
$route.reload()
will prevent the route change from going through.
The distinction between user clicking back or changing the url etc will not make a difference. As browsers don't reload any resources when you alter the url past the #
it's all still contained in your angular logic. This means all these methods should trigger the $locationChangeStart
event.
@Jon pointed out that the $route
service will definitely be of use to you. the .reload()
method will prevent the route change from completing but there is much more you can do with the $route
service if you look into it - see documentation.
Upvotes: 12
Reputation: 7833
This is also nice link http://weblogs.asp.net/dwahlin/cancelling-route-navigation-in-angularjs-controllers
function init() {
//initialize data here..
//Make sure they're warned if they made a change but didn't save it
//Call to $on returns a "deregistration" function that can be called to
//remove the listener (see routeChange() for an example of using it)
onRouteChangeOff = $rootScope.$on('$locationChangeStart', routeChange);
}
function routeChange(event, newUrl) {
//Navigate to newUrl if the form isn't dirty
if (!$scope.editForm.$dirty) return;
var modalOptions = {
closeButtonText: 'Cancel',
actionButtonText: 'Ignore Changes',
headerText: 'Unsaved Changes',
bodyText: 'You have unsaved changes. Leave the page?'
};
modalService.showModal({}, modalOptions).then(function (result) {
if (result === 'ok') {
onRouteChangeOff(); //Stop listening for location changes
$location.path(newUrl); //Go to page they're interested in
}
});
//prevent navigation by default since we'll handle it
//once the user selects a dialog option
event.preventDefault();
return;
}
Upvotes: 4
Reputation: 21249
I have created the following service that incorporates information given in the other answers (CoffeeScript, see Plunker ):
angular.module('myModule').service 'ChangeLossPreventer', ($rootScope, $q, $state) ->
confirmationMessage = null
service =
prevent: (message = 'Changed data will be lost. Continue?') ->
confirmationMessage = message
prompt: ->
confirmation = if confirmationMessage then confirm(confirmationMessage) else yes
if not confirmation
$q.reject()
else
$q.when(confirmation).then ->
service.cancel()
cancel: ->
confirmationMessage = null
$rootScope.$on '$stateChangeStart', (event, newState, newStateParams) ->
if confirmationMessage
event.preventDefault()
service.prompt().then ->
$state.go(newState, newStateParams)
window.onbeforeunload = ->
confirmationMessage
service
It is based on angular-ui-router (usage of the $state
service). When your website enters the state in which data may be lost, you need to call
ChangeLossPreventer.prevent("Optional warning message.");
When it exists this state (i.e. user saves the data), you need to call:
ChangeLossPreventer.cancel()
By default, warning will appear on state change or page close (i.e. refresh or going to another website). You may also need to verify if it is safe to execute another actions (i.e. do not logout with XHR if there are unsaved changes):
$scope.logout = function() {
// confirmation will pop up only if there are unsaved changes
ChangeLossPreventer.prompt().then(function(){
// logout after confirmation
});
};
I have created a Plunker that simulates it.
Upvotes: 0
Reputation: 4400
I would just maintain a property on your $rootScope like hasUnsavedEdits
.
function confirmLeavePage(e) {
var confirmed;
if ($rootScope.hasUnsavedEdits) {
confirmed = $window.confirm("You have unsaved edits. Do you wish to leave?");
if (e && !confirmed) {
e.preventDefault();
}
}
}
$window.addEventListener('beforeunload', confirmLeavePage);
$rootScope.$on("$locationChangeStart", confirmLeavePage);
You might have to tweak the code a bit to handle both conditions. See demo for details.
Upvotes: 4
Reputation: 43526
Demo: http://plnkr.co/edit/Aq8uYg
In the demo, if you change the value of input, you will be noticed when trying to go back.
Listen to $locationChangeStart
and use event.preventDefault()
to cancel the location change if changes not confirmed.
This method has one advantage over $route.reload()
: current controller and models will not be re-instantiated. thus all of your variables are kept the same as user click the "Back" button.
Upvotes: 24