1

Following the answer here, I have the code below:

.directive('confirmOnExit', function($window) {
    return {
        scope: {},
        link: function(scope) {
            var message = "Changes you made may not be saved.";
            $window.onbeforeunload = function(){
                    return message;
            };
            scope.$on('$locationChangeStart', function(event) {
                if(!$window.confirm("Do you want to leave this page? "+message))
                    event.preventDefault();
            });
            scope.$on('$destroy',function(){
                $window.onbeforeunload = null;
            });
        }
    };
})

On Chrome, everything is fine. However, on Firefox, almost every time I click the button of the confirm dialog, the error occurs:

Error: [$rootScope:inprog] $digest already in progress

The solutions I found online mostly suggest to use $timeout. However, event.preventDefault() inside a $timeout function seems not preventing URL change. What should I do?

Community
  • 1
  • 1
Shawn
  • 2,675
  • 3
  • 25
  • 48
  • 1
    what are you trying to do with `scope: {editMode: ' – Claies Jul 14 '16 at 22:30
  • Don't bother with it. It's not related to the issue. I removed it from the code. Thanks. – Shawn Jul 14 '16 at 22:56
  • Not sure if this can be fixed. `confirm` pauses JS thread and may screw the things up. I see `$rootScope:inprog` on Plunker in FF all the time. `$locationChangeStart` listener is executed *within* digest, so there should be no problem with the directive itself as long as it doesn't do $apply or $digest (and it doesn't). `$rootScope:inprog` is probably caused by another piece of code (router, etc). – Estus Flask Jul 14 '16 at 23:11

1 Answers1

0

I just ran into this same problem. For me, calling confirm causes an error in firefox and IE. To get around it, I prevent default immediately if there's a message to show, and run the confirm in a timeout. Then, if the user clicks "leave page", I clear the onbeforeunload and use the $location service to set the url again. If your app is a single page app, $locationChangeStart will be called on the first page load, so you'll want to add in a flag at the top like: if (!hasLoaded) { hasLoaded = true; return; }

$rootScope.$on('$locationChangeStart', function (e, newUrl, oldUrl) {
    // Call the function and show the confirm dialogue if necessary
    if ($window.onbeforeunload) {
        let message = $window.onbeforeunload();

        if (message) {
            // Since we're going to show a message, cancel navigation
            e.preventDefault();
            $timeout(() => {
                if (confirm(message)) {
                    // User chose not to stay. Unregister the function so that it doesn't fire again.
                    $window.onbeforeunload = undefined;

                    // Run the url again. We've cleared onbeforeunload, so this won't create a loop
                    $location.url(newUrl.replace($window.location.protocol + '//' + $window.location.host, ''));
                }
            });
        }
    }
})
frodo2975
  • 10,340
  • 3
  • 34
  • 41