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.
Related
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
Some states in my app require further authentication. Eg a Edit state should prompt the user to enter a one time password.
I would be nice to solve this horizontally: attach metadata to the states requiring extra auth (ie state.requiresExtraAuth: true), and plug some modal logic on the state change events. NOTE: What I am after is that the state does indeed load and the user can see it, but a modal forbids the user from continuing without entering the password.
I tried adding a 'onEnter' property to the toState object param I get in the event listener function, but it didn't work :).
Is something like that possible/how would you go about designing this use case?
In run method of you application.
add.run(function($rootScope, $state, $modal){
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
if(toState.name != 'app.home') {
$state.go('app.login');
// or open modal
$modal.open({...});
}
});
});
This event will help you to check what state user try to access and redirect to login form if needed.
There doesn't appear to be a built-in way to update the url without running all of the other processes, e.g. invoking controllers.
The option "reload", only works with $location.search()
Is there a method, or approach to achieve
How may I achieve something similar to:
$state.go('stateName', params, {justChangeTheUrl: true})
With Angular UI-Router?
If you just want to change the url without having that change processed by ui-router then you can use the following perfectly acceptable hack (coffeescript):
scopeOff = $scope.$on '$stateChangeStart', (e) ->
e.preventDefault()
scopeOff()
$location.path 'just/change/the/url'
Notes
Normally making a change to the browser url via $location.path() would trigger ui-router to begin its processing. This processing starts with the ui-router specific event called $stateChangeStart.
In the code above, however, you are catching and, via e.preventDefault(), throwing away this initial call to $stateChangeStart event and so preventing ui-router from processing the url change.
The watch on the $stateChangeStart event is 'single use', that is the code automatically detaches itself from the event after one run (this is what the call to scopeOff() is doing).
The net effect is to change the url in the browser without triggering ui-router processing and to only apply this restriction to the one url you have just changed. Neat, but not as neat as the new deferIntercept feature that will be with us soon.
Define state parameter in url
$stateProvider.state('stateName', {
url: '/state/path?reload'
});
But be carefull, every $location.search() change on reload param will trigger $stateChangeStart event, and your controller will be recreated
Ramping up on angular, and ui-router
And struggling with redirecting to a different state if a precondition is not met:
I tried using an interceptor: (How do I preform a redirect in an angular interceptor).
But someone mentioned that handling $stateChangeState would be more appropriate. But I am still running into an infinite loop:
/**
* Check here for preconditions of state transitions
*/
$rootScope.$on('$stateChangeStart', function(event, toState) {
// which states in accounts to be selected
var accountRequiredStates = ['user.list', 'user.new'];
if(_.contains(accountRequiredStates, toState.name)){
event.preventDefault();
ApiAccount.customGET('get_current').then(function(resp){
// if I have a selected account, go about your business
if(resp.hasOwnProperty('id')){
$state.go(toState.name);
} else { // prompt user to select account
$state.go('user.select_account');
}
})
}
});
Can anyone suggest a better pattern (one that works)
Thanks!
Note: Similar problem different approach here: How do I preform a redirect in an angular interceptor
I don't think there's anything wrong with the general way you're trying to do this, though I'm not an expert. I do see a flaw in the implementation which looks like it could cause an infinite loop. Let's say the user tries to go to 'user.new' state. Your $stateChangeStart listener intercepts that, cancels it, does your customGET; then if the inner if condition is true (resp.hasOwnProperty('id')), you try to send the user to the same 'user.new' state. At which point, your $stateChangeStart listener intercepts it, cancels it, etc., over and over.
The way I avoid this problem in my code is to have a variable (in the service where I declare the listener) to help me bypass that check:
var middleOfRedirecting = false; Inside your inner if block within the resp.hasOwnProperty('id') check, set middleOfRedirecting to true; add a condition at the start of your $stateChangeStart listener to only call event.preventDefault() and redirect if middleOfRedirecting is false. You also would need a $stateChangeSuccess listener to set middleOfRedirecting back to false, resetting it for the next state change. (I feel like there should be a better way than this, but it at least works.)
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();
});
}