Â
Routing provides a nice way to associate views with controllers in AngularJS using a minimal amount of code. While a user is normally able to navigate directly to a specific route, there may be times when a user triggers a route change before theyâve finalized an important action such as saving data. In these types of situations you may want to cancel the route navigation and ask the user if theyâd like to finish what they were doing so that their data isnât lost. In this post Iâll talk about a technique that can be used to accomplish this type of routing task.
Â
The $locationChangeStart EventWhen route navigation occurs in an AngularJS application a few events are raised. One is named $locationChangeStart and the other is named $routeChangeStart (there are other events as well). At the current time (version 1.2) the $routeChangeStart doesnât provide a way to cancel route navigation, however, the $locationChangeStart event can be used to cancel navigation. If you dig into the AngularJS core script youâll find the following code that shows how the $locationChangeStart event is raised as the $browser objectâs onUrlChange() function is invoked:
Â
$browser.onUrlChange(function (newUrl) { if ($location.absUrl() != newUrl) { if ($rootScope.$broadcast('$locationChangeStart', newUrl,
$location.absUrl()).defaultPrevented) { $browser.url($location.absUrl()); return; } $rootScope.$evalAsync(function () { var oldUrl = $location.absUrl(); $location.$$parse(newUrl); afterLocationChange(oldUrl); }); if (!$rootScope.$$phase) $rootScope.$digest(); } });
The key part of the code is the call to $broadcast. This call broadcasts the $locationChangeStart event to all child scopes so that they can be notified before a location change is made. To handle the $locationChangeStart event you can use the $rootScope.on() function. For this example Iâve added a call to $on() into a function that is called immediately after the controller is invoked:
Â
function init() { //initialize data here..
//Make sure they're warned if they made a change but didn't save it //Call to $on returns a "deregistration" function that can be called to //remove the listener (see routeChange() for an example of using it) onRouteChangeOff = $rootScope.$on('$locationChangeStart', routeChange); }
This code listens for the $locationChangeStart event and calls routeChange() when it occurs. The value returned from calling $on is a âderegistrationâ function that can be called to detach from the event. In this case the deregistration function is named onRouteChangeOff (itâs accessible throughout the controller). Youâll see how the onRouteChangeOff function is used in just a moment.
Â
Cancelling Route NavigationThe routeChange() callback triggered by the $locationChangeStart event displays a modal dialog similar to the following to prompt the user:
Â
Â
Hereâs the code for routeChange():
function routeChange(event, newUrl) { //Navigate to newUrl if the form isn't dirty if (!$scope.editForm.$dirty) return; var modalOptions = { closeButtonText: 'Cancel', actionButtonText: 'Ignore Changes', headerText: 'Unsaved Changes', bodyText: 'You have unsaved changes. Leave the page?' }; modalService.showModal({}, modalOptions).then(function (result) { if (result === 'ok') { onRouteChangeOff(); //Stop listening for location changes $location.path(newUrl); //Go to page they're interested in } }); //prevent navigation by default since we'll handle it //once the user selects a dialog option event.preventDefault(); return; }
Looking at the parameters of routeChange() you can see that it accepts an event object and the new route that the user is trying to navigate to. The event object is used to prevent navigation since we need to prompt the user before leaving the current view. Notice the call to event.preventDefault() at the end of the function. The modal dialog is shown by calling modalService.showModal() (see my previous post for more information about the custom modalService that acts as a wrapper around Angular UI Bootstrapâs $modal service).
If the user selects âIgnore Changesâ then their changes will be discarded and the application will navigate to the route they intended to go to originally. This is done by first detaching from the $locationChangeStart event by calling onRouteChangeOff() (recall that this is the function returned from the call to $on()) so that we donât get stuck in a never ending cycle where the dialog continues to display when they click the âIgnore Changesâ button. A call is then made to $location.path(newUrl) to handle navigating to the target view. If the user cancels the operation theyâll stay on the current view.
ConclusionThe key to canceling routes is understanding how to work with the $locationChangeStart event and cancelling it so that route navigation doesnât occur. Iâm hoping that in the future the same type of task can be done using the $routeChangeStart event but for now this code gets the job done. You can see this code in action in the Customer Manager application available on Github (specifically the customerEdit view). Learn more about the application here.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4