1

I've got a form where I want to prevent the user from navigating away without saving their data back to the server. I borrowed code from here that lets me add a listener to the $locationChangeStartevent. I put the following code in the controller for my form, and lo and behold, nothing happened.

    var cancelRouteListener = $rootScope.$on('$locationChangeStart', checkForUnsavedData);

    function checkForUnsavedData (event, newURL) {
        if (!$scope.form.$dirty) {
            return;
        }

        var proceed = $window.confirm('The page has unsaved changes. Do you want to continue?');

        if (proceed) {
            cancelRouteListener();
            $location.path(newURL);
        }

        event.preventDefault();
        return;
    }

I could see the code reaching the call to $location.path(), but the page navigation didn't change. I then, out of desperation, tried wrapping that line of the code in a call to $rootScope.$apply():

$rootScope.$apply(function () {
    $location.path(newURL);
}

Lo and behold, that worked. I've got a few questions:

  1. Why did I need to use the .$apply() function? I'd have thought the code would run in the context of $rootScope.
  2. Do I really need to attach this to $rootScope? I only need this listener when the form's route is active.

1 Answer 1

1

First, don't you mean $apply()? Second, why would you use $rootScope to kick off a digest cycle instead of $scope within the controller. Third, I don't think you're using the correct logic to achieve the desired functionality. the event $locationChangeStart signals the start of a location change within the Angular Application. $window.confirm() will halt that navigation temporarily while calling event.preventDefault() will stop the navigation all together. Therefore, you should wait for confirmation from $window.confirm(). If the use clicks cancel, proceed will be false. If proceed is false, call event.preventDefault(), otherwise, do nothing. There is absolutely no need for you to call $location.path() when the application is already in the process of navigating to that page.

I've created a Plunk of the scenario here:

http://plnkr.co/edit/qfRVDtIlKqQ1p0pZ4oZ1?p=preview

Sign up to request clarification or add additional context in comments.

3 Comments

First, yes, I meant $apply(). Second, I did that just because it was the first option I came across--I'm posting here because I don't think I need to do that and I want to understand why. Third, I see what you're saying with the logic, because what I'm doing is redundant. However, that's not really an answer I was looking for.
Alright. The $rootScope.$apply() is not necessary at all, and in fact, it shouldn't even be possible. If you're executing this logic within a controller, you should be calling $scope.$on instead of $rootScope.$on, because the $locationChangeStart is a broadcast event and it bubbles downward to all child scopes. That'll save you a dependency injection. You shouldn't be allowed to do this because the event listener will always be called within a digest cycle. Calling $apply() should blow the application up. How is your controller defined?
My controller is defined using the .controller() method on my module object--it's not on the global namespace, if that's what you meant. I think I understand what you're saying--using $apply() on $rootScope could cause plenty of problems. I've refactored the code to use the controller's child scope. It sounds like I was trying to follow a very sloppily coded example, so I'll chalk it up to bad practice and do it the way you've suggested from now on. Thanks!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.