Angular UI Router escaped slashes bug when go to state manually - angularjs

Why Angular UI router in my simple example encode slashes in url if I mannually trigger state change (with $state.go(toState, toParams)).
$rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState, fromParams, error) {
console.log("stateChangeStart");
$rootScope.loading = true;
if(!isLoggedIn) {
event.preventDefault();
Api.get().then(function(response){
isLoggedIn = response;
$state.go(toState, toParams);
});
}
});
Here is full example.
http://plnkr.co/1mSXJbN1PMrFiG72oa9m
For example url like: /template1 is encoded to %252Ftemplate1. Otherwise state is also encoded.
This is really weird, how i can disable that?
In $stateChangeStart event I simulate server side user authentication. Another part of this question is how i can treat url /template1 same as /template1/ in this concrete example?
P.S. I shut down html5 mode since it needs a properly configured server (for testing (bug?) in this example html5 mode is required).
Tnx

Related

Change the URL breaks the routing prevent

I have a requirement to prevent routing if it is first time login user, they have to stay in the setting page to reset password before do something else (or go to other pages), the code is 99% working now, I can change the url/ refresh page, it works fine but only one issue. Here is code:
.state('accountsetting.security', {
url: '/security/{isFirstTimeUser}',
templateUrl: 'settings/security/security.html',
params: {'isFirstTimeUser': null}
}) // this is where I define the route
// in the run block
.run(['$state','$rootScope',function($state,$rootScope) {
// var isFirstTimeUser = false;
// userinforservice.getUserInformation().then(function(data){
// isFirstTimeUser = data.isFirstTimeUser;
// });
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
if(fromState.name!=="undefined" && toState.name=='firsttimeuser'){
$rootScope.isFirstTimeUser=true;
$state.go('accountsetting.security',{isFirstTimeUser:true});
event.preventDefault();
}else if((toParams.isFirstTimeUser || fromParams.isFirstTimeUser) && toState.name !='accountsetting.security'){
$state.go('accountsetting.security',{isFirstTimeUser:true});
event.preventDefault();
}
else{
return;
}
});
}]);
The url is like: https://localhost/app/#/account/security/true
As I mentioned, I can refresh the page or change the url like:https://localhost/app/#/account or https://localhost/app/#
they all work fine, but when I change the url like this:
https://localhost/app/ it will take me to the home page. I check console, in the statechangestart, I lost the isFirstTimeUser, it is undefind. any idea about it?
Thanks in advance.
You lose angular state when you go to the url of the root rather than the state url (i.e) #(hash) urls. The root url reloads the page wherein you lose memory of all javascript variables as they are all client side. Hence the variable is undefined.
State changes happen in a single instance of page load, the url changes give you a illusion as if a page load is happening
The issue causing this behaviour is described by Shintus answer.
A possible solution would be to make sure the event order is correctly resolved. I assume $stateChangeStart is fired before userinforservice.getUserInformation() is resolved. Instead of calling them in parallel you could query the returned promise inside your $stateChangeStart instead of the variable assigned at any undefined time.
.run(['$state','$rootScope',function($state,$rootScope) {
var storedUserPromise;
storedUserPromise = userinfoservice.getUserInformation();
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
storedUserPromise.then(function(data) {
if(data.isFirstTimeUser) {
//do redirection logic here
}
})
});
}]);
Storing the user promise allows you to only have the overhead of calling userinfoservice.getUserInformation() once. Afterwards any .then on the stored promise resolves instantly.
PS: you probably have a typo in userinfo>r<service ;)
You can intercept any route loading in your route definition with $routeProvider.when.resolve, check their status in the resolve block and redirect them or anything else you want to do.
Tutorial Example showing the below code snippet:
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}
}
})

UI-router : $state.current.name shows empty

Sometimes state which i define is not render and shows blank page.
And at this time when i try to see from console which state is this, it shows like below.
state name shows empty, which is not defined in my app.
Anybody know about this issue please help me.
I am guessing you see the full object in console because it gets filled later and the browser reacts..
Try to include your block with $state into a $timeout(). It's a trick to wait for the $digest cycle to be over before getting the value.
$timeout(function() { console.log($state.current.name); });
or
$rootScope.$on('$stateChangeSuccess',
function(event, toState, toParams, fromState, fromParams) {
var statename = toState.name
console.log(statename) })

How to redirect in Angular if translate cookie is not set?

I want to redirect the user to a language page and not allow him to the index page if he hasn't chosen a language yet. I'm using the Angular Translate module. This module has cookie usage built-in with the following function:
$translateProvider.useCookieStorage();
This works. Now I would like to know if this cookie is set, and if not, redirect the user to the language page. Here's my idea how to handle this.
.factory('cookiecheck', function(){
if($translateProvider.CookieStorage.get) return true;
return false;
}
.run(function($rootScope, $state){
if(cookiecheck
{$location.path('/language');}
else
{$location.path('/home');}
This doesn't work. How would I best approach this? What is the correct syntax to determine if a CookieStorage exists and redirect?
You have a few syntax errors in your posted code, the factory isn't necessary as you can inject the $translate service into your run block.
.run(function($rootScope, $state, $location, $translate) {
var store = $translate.storage();
// if store is empty?
if(angular.equals(store, {}))){
$location.path('/language');
}
else {
$location.path('/home');
}
});
See ng-translate docs for cookie store
Also, since you won't know if and when the cookie will be expired or removed i think it is best to watch the route for changes and do your check then.
To do that hook into the $stateChangeStart event from ui router in your run block
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
// check then redirect
});
ui router docs
see this post for watching route changes

Good pattern for a loader in Angular app?

I'm working on an AngularJS-based business app. In most common scenario before I show the view I'm loading some data by making a few $http POST calls. I want to show a loader in the meantime. So far I've done it by $broadcasting an event and catching it elsewhere with dedicated controller. This allows me to have a single loader per web page, which is fine. At least for now.
But maybe are there any better approaches?
Instead of broadcasting the events yourself you can take advantage of the start and finish events that the route provider are throwing apon view-change.
Angular router ($route)
$rootScope.$on('$routeChangeStart', function(e) {
openLoader();
});
$rootScope.$on('$routeChangeSuccess', function(e) {
closeLoader();
});
ui-router ($state)
$rootScope.$on('$stateChangeStart', function(e, toState, toParams, fromState, fromParams) {
openLoader();
});
$rootScope.$on('$stateChangeSuccess', function(e, toState, toParams, fromState, fromParams) {
closeLoader();
});
And you might want to add a filter here if you have states/routes where the loader should not be displayed.
The approach:
You can have a block level element in your main page containing gif loader or "Loading..." text. The visibility of this element should be hidden by default.
Inside Request Interceptor you can make the block level element visible and inside Response interceptor if all the request are completed and there are no pending requests you can hide this block level element.
You can achieve this functionality in a common place and so far have found this as a best approach

Can't get $anchorScroll working on $stateChangeSuccess?

I'm trying to adapt the answer give in this similar question How to handle anchor hash linking in AngularJS. I need to be able to use it with ui-router. I've tested anchor scrolling as a clickable function in my controller and that is working. Here is the code for that
$scope.anchor = function () {
console.log('test');
$location.hash('comments');
$anchorScroll();
};
If I try to invoke that function immediately nothing happens
$scope.anchor();
If I try to invoke it on a $stateChangeSuccess nothing happens. I threw in a log for sanity and that is firing. I also tried a trick to prevent further routing logic from kicking in. It was mentioned in the linked post but I'm not sure if it's necessary for my case.
app.run(function ($rootScope, $location, $stateParams, $anchorScroll) {
// allow anchorScrolling
$rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
console.log('stateChangeSuccess');
var old = $location.hash();
$location.hash('comments');
$anchorScroll();
//reset to old to keep any additional routing logic from kicking in
$location.hash(old);
});
});
I'm guessing that I'm running into race conditions or my anchor scroll is somehow triggering another route change. But I can't figure out how to track these issues down. Any thoughts?
EDIT: Update I removed the $location.hash(old); and put the rest of the $anchorScroll pieces into a timeout function and it's now working. I also added in the $stateParams.scrollTo to make this useable with query params. Any ideas as to what is causing my race condition and how this might be solved without a $timeout function?
$rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
console.log('stateChangeSuccess');
$timeout(function() {
$location.hash($stateParams.scrollTo);
$anchorScroll();
}, 300);
});
From $anchorScroll documentation
It also watches the $location.hash() and scrolls whenever it changes to match any anchor. This can be disabled by calling $anchorScrollProvider.disableAutoScrolling().
Try removing $location.hash(old);
If that fixes it, use $anchorScrollProvider.disableAutoScrolling() to disable it reacting to another state change.

Resources