Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS window.onbeforeunload in one controller is being triggered on another controller

Here is my problem, I have two views (View1 and View2) and a controller for each view (Ctrl1 and Ctrl2). In View1 I'm trying to warn the user before he leaves the page accidentally without saving changes.

I'm using window.onbeforeunload, which works just fine, but my problem is that even though I have onbeforeunload only on one controller, the event seems to be triggered in the second controller as well! But I don't have even have that event in there. When the user leaves a page and there are unsaved changes, he gets warned by the onbeforeunload, if the user dismiss the alert and leaves the page, is he taken back to the second view, if the user tries to leave now this other view, we would also get warned about unsaved changes! When that's not even the case! Why is this happening?

I'm also using $locationChangeStart, which works just fine, but only when the user changes the route, not when they refresh the browser, hit 'back', or try to close it, that's why I'm forced to use onbeforeunload (If you a better approach, please let me know). Any help or a better approach would be greatly appreciated!

//In this controller I check for window.onbeforeunload
app.controller("Ctrl1", function ($scope, ...)){
  window.onbeforeunload = function (event) {        

    //Check if there was any change, if no changes, then simply let the user leave
    if(!$scope.form.$dirty){
      return;
    }

    var message = 'If you leave this page you are going to lose all unsaved changes, are you sure you want to leave?';
    if (typeof event == 'undefined') {
      event = window.event;
    }
    if (event) {
      event.returnValue = message;
    }
    return message;
  }

  //This works only when user changes routes, not when user refreshes the browsers, goes to previous page or try to close the browser
  $scope.$on('$locationChangeStart', function( event ) {    
    if (!$scope.form.$dirty) return;
    var answer = confirm('If you leave this page you are going to lose all unsaved changes, are you sure you want to leave?')
    if (!answer) {
      event.preventDefault();
    }
  });
}

//This other controller has no window.onbeforeunload, yet the event is triggered here too!
app.controller("Ctrl2", function ($scope, ...)){
  //Other cool stuff ...
}

I tried checking the current path with $location.path() to only warn the user when he is in the view I want the onbeforeunload to be triggered, but this seems kinda 'hacky' the solution would be to not execute onbeforeunload on the other controller at all.

I added $location.path() != '/view1' in the first which seems to work, but it doesn't seem right to me, besides I don't only have two views, I have several views and this would get tricky with more controllers involved:

app.controller("Ctrl1", function ($scope, ...)){
  window.onbeforeunload = function (event) {        

    //Check if there was any change, if no changes, then simply let the user leave
    if($location.path() != '/view1' || !$scope.form.$dirty){
      return;
    }

    var message = 'If you leave this page you are going to lose all unsaved changes, are you sure you want to leave?';
    if (typeof event == 'undefined') {
      event = window.event;
    }
    if (event) {
      event.returnValue = message;
    }
    return message;
  }

  //This works only when user changes routes, not when user refreshes the browsers, goes to previous page or try to close the browser
  $scope.$on('$locationChangeStart', function( event ) {    
    if (!$scope.form.$dirty) return;
    var answer = confirm('If you leave this page you are going to lose all unsaved changes, are you sure you want to leave?')
    if (!answer) {
      event.preventDefault();
    }
  });
}
like image 992
Poncho Avatar asked Aug 27 '14 17:08

Poncho


4 Answers

Unregister the onbeforeunload event when the controller which defined it goes out of scope:

$scope.$on('$destroy', function() {
    delete window.onbeforeunload;
});
like image 131
Jawher Avatar answered Oct 17 '22 08:10

Jawher


I just attempted the above solution, and that wasn't working for me. Even manually typing delete window.onbeforeunload in the console wouldn't remove the function. I needed to set the property to undefined instead.

$scope.$on('$destroy', function(e){
  $window.onbeforeunload = undefined;
});
like image 36
Nick Fitzpatrick Avatar answered Oct 17 '22 08:10

Nick Fitzpatrick


For those of you using angular-ui-router you would use is $stateChangeStart instead of $locationChangeStart, e.g.

$scope.$on('$stateChangeStart', function (event){
  if (forbit){
    event.preventDefault()
  }
  else{  
    return
  }
})
like image 8
Santiago Angel Avatar answered Oct 17 '22 08:10

Santiago Angel


As an update to this, for anyone used angular-ui-router I'd now pass $transitions into your controller and use;

$transitions.onStart({}, function ($transition)
{
    $transition.abort();
    //return false;
});
like image 4
Shawson Avatar answered Oct 17 '22 10:10

Shawson