Prevent a stateChange with angular ui router without using $rootScope - angularjs

My user can leave a state but before I want to show a modal dialog "Do you want to save?"
ONLY if the user data is dirty that means changed.
What I do NOT want is to stick a isDirty property in my EditController to the $rootScope go to the stateChangeStart event and check there for isDirty and then show/not the save dialog.
Prevent global variables says every javascript beginner book...
1.) What is then the pro way to prevent a state change without hacking the $rootscope?.
2.) Are there any helper libraries for ui-router which enhance the ui-router offering function hooks inside the controller to encapsulate the ui logic?

(1) According to the docs under State Change Events
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams){
event.preventDefault();
// transitionTo() promise will be rejected with
// a 'transition prevented' error
})
You could change $rootScope to $scope wherever appropriate and works.
Under Attach Custom Data to State Objects, you can pass on custom data.
(2) I'm not sure what you're asking but factories/services/providers would really help.

Using $transitions.onStart (angular-ui-router 1.0.0-rc) you can return a boolean. If false the transition will be cancelled.
$transitions.onStart({}, function (trans) {
var answer = confirm("Want to leave this page?")
if (!answer) {
return false;
}
});
Here is the documentation: https://ui-router.github.io/ng1/docs/latest/modules/transition.html#hookresult

Though at the time of writing it is not a part of the stable release, the 1.0 release of the UI-router will use the return value of onEnter/onExit to prevent navigation.
See GitHub issue 2219

Related

Angular navigate to the previous state on page reload

I wanted to know if there is a way in Angular to refresh a single page application and properly go back to the same state as before? I am using ui-router (version 0.2.15) for defining different states.
Currently, I am storing the state name using ngStorage and have placed $state.go(last_stored_state_name) in the controller of the state that loads first. This way I get to the state that i was on before refresh but the problem is i can see the first state's view just for a second before $state.go(last_stored_state_name) runs.
Can anyone please tell me how I can bypass the first state's view directly to the desired state?
Try this:
$state.go($state.current, {}, {reload: true});
Create a controller and add it with ng-controller at any top level element like on <body> tag so that its scope is available throughout the application and in that controller, do like this:
// Event listener for state change.
$scope.$on('$stateChangeStart', function(event, toState, toParams, fromState) {
if (last_stored_state_name && isPageRefresh) {
// prevent execution of current state and navigate to last saved state
event.preventDefault();
$state.go(last_stored_state_name);
return;
}
});
So you don't have to do $state.go(last_stored_state_name); in your controller. This way, you will not see the first state for a second.
You can figure out a variable isPageRefresh with any type of checks like checking if fromState is null or setting a local storage variable or setting a global javascript variable .

Angular - How to check if user is allowed before loading the view?

I have an angular app. I am using ui-router to manage my states. I need to call a common API to check whether the user is allowed to view the web page or not. Is there a common place where I can call the API and only then proceed to the state the user is requesting for?
With my current implementation, the view partially gets displayed and then the reroute happens.
//app.js
$rootScope.$on('$stateChangeSuccess', function(evt, toState) {
if(toState.name === 'login' || toState.name === 'payment')
return;
userService.isBillingCleared().then(function() {
//redirect to toState
}, function() {
$state.go('payment');
});
});
How do I prevent this?
Try the following
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams, options) {
if(toState.name === 'login' || toState.name === 'payment')
return;
event.preventDefault(); // prevent the state change
userService.isBillingCleared().then(function() {
$state.go(toState.name); // do the state change manually if allowed
}, function() {
$state.go('payment');
});
}));
Try this link which talks about authentication for routes in ui-router. Seems like this would be a good solution for you.
I was facing similar issue while developing a system and the only difference was that I was using default router that is provided by angular but obviously that wont cause any problem.
Keep the data hidden before checking the authentication level of the user and make directive to centralize the whole process. Let's imagine it this way - the view in question that you'll be showing to the user is hidden unless the details are being checked and until then show them any preloader or something or whatever animation you want to.
This is what generally people do in case of a web app or any mobile application, clog the data unless you are sure that yes the data is ready to be viewed.
This practice can be useful even in use case where you are loading the data after the page is being loaded, for e.g. let's say I am showing transactions to user but the transactions are loaded only after the user calls for the view so a call to the backend is made when the view is called that means the user can see for a split second only - an empty table. So...just make use of a preloader and call for the function first and then let the user see the table.

With angularjs and ui-router, how to disable the navigator history for one view change

I want the current state change not to be saved in navigation history. Then when user clicks 'back', the view will be skipped and go back to previous page.
Can I temporally disable the state change to be saved in navigator history?
I believe this where ui-router's option parameter with value { location:'replace'} is intended for.
The documentation explains:
location Boolean or "replace" (default true), If true will update the
url in the location bar, if false will not. If string "replace", will
update url and also replace last history record.
It seems that there's an issue (see it here on GitHub) to be fixed though.
In the spirit of stackoverflow I will try to answer the question and not mention that what you are asking for is a truly horrible hack ;-)
I think you may need something like this. (the code is coffeescript)
angular
.module 'app', ['ui.router']
.run ($rootScope, $state) ->
$rootScope.$on '$locationChangeStart', (event) =>
if $state.is 'bad-state'
event.preventDefault()
$state.go 'good-state'
When the location changes (you could also use $stateChangeStart) you check the current state and if it is the state you don't want then you go to the state you do want. the event.preventDefault() effectively halts the processing of the originally requested state.
If you need an even dirtier version then you could check the location urls instead of the states and use $location.url to perform the transfer, like this:
angular
.module 'app', ['ui.router']
.run ($rootScope, $location) ->
$rootScope.$on '$locationChangeStart', (event, nextLocation) =>
if nextLocation is '/bad/location'
event.preventDefault()
$location.path '/good/location'
This worked for me.
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams){
event.preventDefault();
window.history.forward();
});

AngularJS and ui.route seamless navigation

I'm having an issue. I'm building a Angular App, I'm using angular ui router.
I want to stop the navigation from one page to another until I'm running a dom animation
I know I have onStateSuccess and onStateStart but I don't know how to stop going to that state until I ran an animation.
I know I can navigate with $state.go('localhost') but I am looking for a better approach to stop and resume the event.
Any ideas?
EDIT
I see that I can stop the event on change start, is there a way to resume it after I ran a few functions?
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
console.log(event);
console.log(toState);
console.log(toParams);
console.log(fromState);
console.log(fromParams);
event.preventDefault();
// transitionTo() promise will be rejected with
// a 'transition prevented' error
});
On $stateChangeStart check to see if you are currently animating (using a flag). If you are not, set a flag to indicate that you are animating.
If you are animating, push the new requested view into an array. Returning false will cancel the request.
On $stateChangeSuccess set the animating flag to false and check the array mentioned above for requested views, if it has something $state.go() to it.
UPDATE
At the start of your animations set a flag to true and use that to prevent the page from changing as noted above. Set a timeout for how long your longest animation is. The timeout would them set the animation flag to false and trigger the real state change.

AngularJS - Detecting, stalling, and cancelling route changes

I can see the event $routeChangeStart in my controller, but I don't see how to tell Angular to stay. I need to popup something like "Do you want to SAVE, DELETE, or CANCEL?" and stay on the current "page" if the user selects cancel. I don't see any events that allow listeners to cancel a route change.
You are listening to the wrong event, I did a bit of googling but couldn't find anything in the docs. A quick test of this:
$scope.$on("$locationChangeStart", function(event){
event.preventDefault();
})
In a global controller prevented the location from changing.
The documented way of doing this is to use the resolve property of the routes.
The '$route' service documentation says that a '$routeChangeError' event is fired if any of the 'resolve' promises are rejected.1 That means you can use the '$routeProvider' to specify a function which returns a promise that later gets rejected if you would like to prevent the route from changing.
One advantage of this method is that the promise can be resolved or rejected based on the results of asynchronous tasks.
$scope.$watch("$locationChangeStart", function(event){
event.preventDefault();
});
You can do like this as well. Benefit of doing this way is that it
doesn't trigger through if() statement with $on ...as you well see
that below code will trigger no matter what the condition is:
if(condition){ //it doesn't matter what the condition is true or false
$scope.$on("$locationChangeStart", function(event){ //this gets triggered
event.preventDefault();
});
}

Resources