Angular JS Routing Interception and Redirection Failure Reply

Mike BerrymanRecently I had built an Angular JS application that would prompt the user for some information in a popup whenever they tried to navigate to a specific route, before that route’s page actually shows to the user.  If the user provided some invalid data or they cancelled/closed the popup I needed the app to either a) take the user back to the route they were on or b) redirect to the home page of the app if they weren’t coming from a route within the app.

This angular application used the default ngRoute module to handle routing.  When defining the route that needs to prompt the user for some info before allowing them to continue to the route’s page, I basically used the “resolve” property of the route to open a popup (which returned a promise) and if the user closes the popup or provides bad data, reject the popup’s promise.  Rejecting any promise used in the “resolve” property of a route raises the “$routeChangeError” event and cancels the navigation.  Attaching a handler to this event is how I was able to take the user back to their previous route, or redirect to the home page of the app.

My route change error handler originally looked something like this:

angular.module("MyApp", ["ngRoute"]).run(function($rootScope, $location) {
    $rootScope.$on("$routeChangeError", function(error, newRoute, oldRoute, rejectArg) {
        if (oldRoute) {
            $window.history.back();
        }
        else {
            $location.path("/");
        }
    });
});

For redirecting the user to the home page this worked great.  However, home page redirection is an edge-case in this application, and the vastly more likely redirect to previous route was not working.  It kept re-prompting with the popup they had just cancelled/gave invalid data to!  To determine if the problem lie with the popup or the route changing, I got rid of the popup from the “resolve” property of the route and force the promise that the popup would have returned to fail.  When I did that I was getting the error “10 $digest() iterations reached.  Aborting!”, so it seemed like the application would have gotten stuck in an infinite loop had angular not caught it.

After doing some research it turns out this would only happen in IE (of course, that’s what my client uses and what I was developing against).  It seems that in IE the browser changes the Url before triggering the route change event.  As you can see in the code snippet above when I want the user to return to the route they came from I use the $window.history.back() function.  Apparently because IE was changing the Url before the route change triggered, $window.history.back() would try to take the user to the  current route despite never having actually finished loading that Url.  Of course this would result in constantly trying to load the route that prompts the user with the popup.

The workaround was to wrap the $window.history.back() call in a timeout. The timeout doesn’t even have to wait any amount of time – just wrapping the call in a timeout block moves its execution to the end of the current processing cycle.  I’m not 100% sure why this works but I’m guessing IE would see that the 2 most recent entries in its history list are the same and interpret that as a refresh instead of a new history entry and thus remove one of the duplicates from the history list.  You just need to give it a cycle to perform this processing.  Regardless of why it works, the fact is it does fix the issue.  Here’s what my code looked like afterwards:

angular.module("MyApp", ["ngRoute"]).run(function($rootScope, $location, $timeout) {
    $rootScope.$on("$routeChangeError", function(error, newRoute, oldRoute, rejectArg) {
        if (oldRoute) {
            $timeout(function() {
                $window.history.back();
            }, 0);
        }
        else {
            $location.path("/");
        }
    });
});

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s