59

I'm an angularjs new bee. I'm trying to write a validation which alerts the user when he tries to close the browser window.

I have 2 links on my page v1 and v2.When clicked on the links it takes to the specific pages. Here is the code to redirect to v1 and v2

angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives'])

.config(['$routeProvider', function($routeProvider) {
        $routeProvider.when('/v1', {templateUrl: 'pages/v_1.html', controller: MyCtrl1});
        $routeProvider.when('/v2', {templateUrl: 'pages/v_2.html', controller: MyCtrl2});
        $routeProvider.otherwise({redirectTo: '/v1'});
}]);

I want to pop up a message when the user clicks on v1 that "he's about to leave from v1, if he wishes to continue" and same on clicking on v2. Any pointers on how to achieve this would be appreciated.

I got an answer here but it pops up the message after every time interval.

Updated Code;

Controllers

function MyCtrl1() {
    $scope.$on('$locationChangeStart', function (event, next, current) {
        if ('your condition') {
            event.preventDefault();

            MessageService.showConfirmation(
                'Are you sure?',
            MessageService.MessageOptions.YES_NO, {
                'YES': function () {
                    blockNavigation = false;
                    $location.url($location.url(next).hash());
                    $rootScope.$apply();
                },
                'NO': function () {
                    MessageService.clear();
                    $log.log('NO Selected')
                }
            });
        }
    });
}
MyCtrl1.$inject = [];


function MyCtrl2() {}
MyCtrl2.$inject = [];
Community
  • 1
  • 1
iJade
  • 23,144
  • 56
  • 154
  • 243
  • 12
    I believe the term you wanted is "[newbie](http://www.urbandictionary.com/define.php?term=newbie)". Unless you're actually an apis mellifera pupa. – Sean the Bean Dec 15 '15 at 16:22

6 Answers6

106

The code for the confirmation dialogue can be written shorter this way:

$scope.$on('$locationChangeStart', function( event ) {
    var answer = confirm("Are you sure you want to leave this page?")
    if (!answer) {
        event.preventDefault();
    }
});
Scheintod
  • 7,953
  • 9
  • 42
  • 61
  • 5
    this is a cleaner solution. The other one led me to an infinite loop of navigate confirmation... – wakandan Apr 03 '14 at 04:24
  • 1
    There also could be a handler for standard JavaScript event, that listens for resource unloading. I.e. when it is not a SPA app. The listener could be assigned to `window.onbeforeunload`. – Eugene Mar 02 '15 at 08:45
  • 5
    Sadly, this doesn't work when you are using route servce, the event is triggered everytime the location changes.. – vdegenne May 20 '15 at 09:11
  • 34
    When using Angular ui-router : use '$stateChangeStart' instead of '$locationChangeStart' – OlivierLarue Jun 18 '15 at 09:08
  • I coupled this with a .$off [extension](http://stackoverflow.com/questions/14898296/how-to-unsubscribe-to-a-broadcast-event-in-angularjs-how-to-remove-function-reg) that really help separate concerns – Rick Jul 13 '15 at 17:50
  • 3
    Am I the only one for whom this code comes up as soon as I open a page, instead of when I navigate away? – Chris May 28 '16 at 15:26
  • 1
    This works when navigating away, but not when refreshing the current page. – Wouter Mar 11 '17 at 12:27
45

Lets seperate your question, you are asking about two different things:

1.

I'm trying to write a validation which alerts the user when he tries to close the browser window.

2.

I want to pop up a message when the user clicks on v1 that "he's about to leave from v1, if he wishes to continue" and same on clicking on v2.

For the first question, do it this way:

window.onbeforeunload = function (event) {
  var message = 'Sure you want to leave?';
  if (typeof event == 'undefined') {
    event = window.event;
  }
  if (event) {
    event.returnValue = message;
  }
  return message;
}

And for the second question, do it this way:

You should handle the $locationChangeStart event in order to hook up to view transition event, so use this code to handle the transition validation in your controller/s:

function MyCtrl1($scope) {
    $scope.$on('$locationChangeStart', function(event) {
        var answer = confirm("Are you sure you want to leave this page?")
        if (!answer) {
            event.preventDefault();
        }
    });
}
Dmitry Gonchar
  • 1,642
  • 2
  • 16
  • 27
Yair Nevet
  • 12,725
  • 14
  • 66
  • 108
22

Here is the directive I use. It automatically cleans itself up when the form is unloaded. If you want to prevent the prompt from firing (e.g. because you successfully saved the form), call $scope.FORMNAME.$setPristine(), where FORMNAME is the name of the form you want to prevent from prompting.

.directive('dirtyTracking', [function () {
    return {
        restrict: 'A',
        link: function ($scope, $element, $attrs) {
            function isDirty() {
                var formObj = $scope[$element.attr('name')];
                return formObj && formObj.$pristine === false;
            }

            function areYouSurePrompt() {
                if (isDirty()) {
                    return 'You have unsaved changes. Are you sure you want to leave this page?';
                }
            }

            window.addEventListener('beforeunload', areYouSurePrompt);

            $element.bind("$destroy", function () {
                window.removeEventListener('beforeunload', areYouSurePrompt);
            });

            $scope.$on('$locationChangeStart', function (event) {
                var prompt = areYouSurePrompt();
                if (!event.defaultPrevented && prompt && !confirm(prompt)) {
                    event.preventDefault();
                }
            });
        }
    };
}]);
Christopher Davies
  • 4,461
  • 2
  • 34
  • 33
  • 2
    I had to use `$(window).bind('beforeunload', doPrompt);` rather than `window.addEventListener('beforeunload', areYouSurePrompt);` (and `unbind` for `removeEventListener`) in FF 31.0 for whatever reason as the native `window` calls weren't working (while `window.onbeforeunload = function(){ return "sure?"; }` worked)?! Also from what I can tell, the `$locationChangeStart` is never firing and considering the use of `onbeforeunload` is this block really necessary? – Campbeln Aug 27 '14 at 01:49
  • @Campbeln upvoted for the tip, the code above also didn't work on Chrome42 I had to do the as you mentioned. – Adriano Rosa Apr 24 '15 at 03:38
9

As you've discovered above, you can use a combination of window.onbeforeunload and $locationChangeStart to message the user. In addition, you can utilize ngForm.$dirty to only message the user when they have made changes.

I've written an angularjs directive that you can apply to any form that will automatically watch for changes and message the user if they reload the page or navigate away. @see https://github.com/facultymatt/angular-unsavedChanges

Hopefully you find this directive useful!

facultymatt
  • 169
  • 1
  • 3
  • 8
2

The other examples here work fine for the old versions of ui-router (>=0.3.x) but all state events, such as $stateChangeStart, are deprecated as of 1.0. The new ui-router 1.0 code uses the $transitions service. So you need to inject $transitions into your component then use the $transitions.onBefore method as the code below demonstrates.

$transitions.onBefore({}, function(transition) {
  return confirm("Are you sure you want to leave this page?");
});

This is just a super simple example. The $transitions service can accept more complicated responses such as promises. See the HookResult type for more information.

Brent Matzelle
  • 4,073
  • 3
  • 28
  • 27
-1
$scope.rtGo = function(){
            $window.sessionStorage.removeItem('message');
            $window.sessionStorage.removeItem('status');
        }

$scope.init = function () {
            $window.sessionStorage.removeItem('message');
            $window.sessionStorage.removeItem('status');
        };

Reload page: using init