Transition to state ui-router - angularjs

I have a http interceptor which has to redirect to the login page if it has a statuscode of 401.
The code is as follow:
responseError: function(response) {
if (response.status === 401) {
$window.sessionStorage.removeItem('token');
$injector.get('$state').go('guest.login');
}
return response || $q.when(response);
}
Here I have the guest.login state defined:
.state('guest', {
abstract: true
})
.state('guest.login', {
templateUrl: 'views/login.html'
url: '/login',
controller: 'LoginCtrl'
})
When I the request returns a 401 error, I get a error:
Could not resolve 'guest.login' from state '...'
I've tried both $state.go and $state.transitionTo.
How am I suppose to go to my login page with ui-router?

Every state tries to populate the ui-view of its parent state. 'guest.login' has the parent 'guest' but the 'guest' state has no ui-view. Adding it in should fix this. This is a common pitfall with ui-router abstract states. I wish we had some better errors here (any errors).
state('guest', {
abstract: true,
template: '<ui-view/>'
})
Read the "Remember" part of this section: https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#abstract-states

Related

AngularJS and ui-router: wrong routes behaviour after refresh

I'm developing an application with AngujarJS and ui-router and I'm having a problem loading the routes. To facilitate the explanation I will delete parts of the code. My rotes file looks like this:
app.config(function($stateProvider, $urlRouterProvider,
$locationProvider) {
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl : 'pages/dashboard.html',
controller : 'DashController'
})
.state('dashboard.vendas', {
url: '/vendas',
templateUrl : 'pages/vendas.html',
controller : 'vendasController'
})
;
$urlRouterProvider.otherwise('/');
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
});
I load this route /dashboard after the user login. On this route, I load, among other things, an http method that returns the companies that the user will have access to, through the factory below:
app.factory("factoryEmpresas",
function($http,urlBase,isAuthenticated) {
return {
getEmpresas: function() {
return $http({
url: urlBase.getUrl() + '/empresas',
method: "GET",
headers: {
'X-Token': isAuthenticated.getJWT()
}
}).then(function successCallback(response) {
return response.data;
}, function errorCallback(response) {
return response;
});
}
}
});
When accessing the route /vendas (which is related to the state dashboard.vendas), I make another http call using a variable that is in the factoryEmpresas return as follows:
app.factory("factoryVendas",
function($http,urlBase,isAuthenticated) {
return {
getVendas: function(id_empresa) {
return $http({
url: urlBase.getUrl() + '/vendas?id_empresa=' + id_empresa,
method: "GET",
headers: {
'X-Token': isAuthenticated.getJWT()
}
}).then(function successCallback(response) {
return response.data;
}, function errorCallback(response) {
return response;
});
}
}
});
Everything works perfectly, until the user decides to refresh (F5) when on the route /vendas. At that moment the loading of the two routes /dashboad and /vendas is triggered. However, when making the http call at factoryVendas, the factoryDashboard has not yet returned the getEmpresas function. Thus, the variable "empresa_id" is not filled with the value that it should, thus resulting in an undefined call in place of "empresa_id".
The question I ask is, how can I make factorySales wait for the factoryDashboard to return so that I can make the call with the variable duly filled in?
Any help will be welcome.
Thank you!
It sounds like vendas has a direct dependency on empresa. Would it make sense to make empresaId part of the route parameters for accessing vendas? That way the information needed to load the page would always be accessible (from $stateParams), even on reload
.state('dashboard.vendas', {
url: '/:empresaId/vendas/',
templateUrl : 'pages/vendas.html',
controller : 'vendasController'
});
The other thing you could do is define load order by creating a resolve on the parent that the child consumes. Then uirouter knows it needs to wait for the parent resolve before attempting to load the child.
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl : 'pages/dashboard.html',
controller : 'DashController',
resolve: {
Empresas: function(factoryEmpresas) {
return factoryEmpresas.getEmpresas();
}
}
})
.state('dashboard.vendas', {
url: '/vendas',
templateUrl : 'pages/vendas.html',
controller : 'vendasController',
resolve: {
Vendas: function(Empresas, factoryVendas) {
var id_empresa = Empresas[0].id; // just kind of guessing here
return factoryVendas.getVendas(id_empresa);
}
}
})
Vendas and Empresas can be injected into their respective controllers.
While this would work I'd actually discourage this method because now your user is waiting around more for content to appear. Studies have shown its better to show your users incremental progress, your app will feel faster. I try to avoid resolves when possible and have initialization logic in my controller. That way, the page loads immediately and fills in as data becomes available.

Unable to retrieve the error associated and display in login page

Using AngularJS and Spring Security, when the authentication fails there is a redirect to the login page with error=true. I need to display this error on login page (AngularJS). I don't know how to do this.
Below is the declaration of authentication-failure-url in ssecurity-context.xml:
<security:form-login login-page="/login"
login-processing-url="/authenticate"
authentication-failure-url="/login?error=true"
username-parameter="username" password-parameter="password"/>
Could some please tell me how to retrieve the error and show on login page?
JS
angular.module('app.services')
.service('AuthenticateService'‌​,['$http','$location‌​',function($http,$lo‌​cation)
{
return
{
login : function(uname, pword)
{
var data = "username="+uname+"&password="+pword;
return $http.post("authenticate",data,
{
headers: { "Content-Type": "application/x-www-form-urlencoded" } })
.then(function (response)
{
console.log(response.status);
},function(error)
{
console.log(error.status);
});
}
}
}]);
When your login attempt fails, you're redirecting to /login/error/true
Then, you should have on your angularJS a route to that URL with the partial HTML that you want to display, alongside with an angularJS controller which will handle or do the appropriate actions upon that URL.
For example, let's say that your route file will look this:
var myApp = angular.module("myApp", [ "ngRoute" ]);
myApp.config(function ($routeProvider) {
$routeProvider
.when("/login/error/:errValue",
{controller: "LoginController", templateUrl: "/partials/login.html"})
});
Now all you have to do is tho check on LoginController whether :errValue is true or false. and act upon that value.
with wrong credentials, I tried checking the value of errValue in the controller, but its says undefined
Below are the routes declared
$stateProvider
.state("/login",{ //this for login page
url:'/login',
templateUrl:'./resources/js/baseapp/ContactLogin/views/contactLogin.html',
controller:'contactLoginCtrl'
})
.state("/home",{
url:'/home', // this is for home page
templateUrl:'./resources/js/baseapp/ContactHome/views/ContactHome.html',
controller:'contactHomeCtrl'
})
.state("/login:error/:errValue",{ // this is for login error page
url:'/home',
templateUrl:'./resources/js/baseapp/ContactLogin/views/contactLogin.html',
controller:'contactLoginCtrl'
})
$urlRouterProvider.otherwise("/login");

Navigating to url directly after removing hashbangs not working

I am using angular for my app
I wanted to remove the # from the url so i added the below lines as suggested in SO answer
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
In the index.html I also added the below code as suggested here
<head>
<base href="/">
</head>
It all works fine when I navigate to pages from the home, but when i copy the url and open it in new tab, it throws 404 error
Example
When I launch the app, it's opening http://localhost:portno/home.
When I refresh the page, I'm getting a 404 error.
What other configuration should i make?
My code structure is as below
.state('tab.home', {
url: '/home',
views: {
'tab-home': {
templateUrl: 'templates/tab-home.html',
controller: 'templeHome'
}
}
})
.state('tab.list', {
url: '/list',
views: {
'tab-home': {
templateUrl: 'templates/list.html',
controller: 'templeList'
}
}
})
You need to add a route on your server that will redirect you to the entrypoint of your front (i.e: index.html).
For example, if you were redirected from your home to http://localhost:portno/foo/bar, you'll need a route to match the /foo/bar one that will redirect you to your index.html.
It migth look like this (note that this is an example code of my own written for Hapi):
server.route([
{
method: 'GET',
path: '/foo/bar',
handler: function(request, reply) {
reply.file('./public/index.html');
}
}
...

Angular.js: page flickering on authentication redirect

I'm implementing some simple client-side authentication logic in Angular.js. The pages involved are:
/account#/login (public)
/account (require login)
/account#/settings (require login)
When a user is not logged in and try to visit either /account or /account/#/settings, the app is supposed to redirect to the login page.
I have the following routes configured using ui-router:
$stateProvider
.state('overview', {
url: '/',
restricted: true
})
.state('settings', {
url: '/settings',
restricted: true
})
.state('login', {
url: '/login',
restricted: false
})
and upon URL change, I check if the upcoming page is a restricted page and whether the current user is not logged in. If so redirect to login.
app.run(function($rootScope, $location, $state, auth) {
$rootScope.$on('$stateChangeStart', function(event, next) {
if (next.restricted && !auth.isLoggedIn()) {
event.preventDefault();
$state.go('login');
}
});
});
auth is just a service that checks the login status and returns either true (logged in) or false (not logged in).
Here's my question:
Even though this (kind of) works, I see a page flickering issue when trying to visit a restricted page while not logged in. The page flashes the contents of the restricted page quickly before redirecting me to the login page.
I did a little bit researching online and some people have mentioned the potential solution could be using resolve when defining my states, since the page won't load unless it resolves successfully. However, when I try to add
resolve: {
load: function(auth) {
return auth.isLoggedIn();
}
}
It didn't work. What am I missing? Is using resolve the way to go?
The way you are currently doing it will check if the user is logged in or not and set load to true or false. Also controller gets instantiated before load is resolved which is why you see the flickering. You need to achieve two things here:
Make sure that load is resolved before the controller is instantiated.
If user is not logged in, redirect the user to the login page.
For the first part we need to use a promise as it will be resolved and converted to value before controller is instantiated. This is what the documentation says:
If any of these dependencies are promises, they will be resolved and
converted to a value before the controller is instantiated and the
$stateChangeSuccess event is fired.
Following code can do that for us:
var isLoggedin = ['auth', '$q',
function(auth, $q) {
var deferred = $q.defer();
//assuming auth.isLoggedIn returns a promise
var loginPromise = auth.isLoggedIn();
loginPromise.then(
function(response) {
deferred.resolve(response);
},
function(error) {
deferred.reject('Not logged in');
});
return deferred.promise;
}
];
And states will use isLoggedin:
$stateProvider
.state('overview', {
url: '/',
resolve: {
loggedin: isLoggedin
}
})
.state('settings', {
url: '/settings',
resolve: {
loggedin: isLoggedin
}
})
.state('login', {
url: '/login'
})
For the second problem, that is redirecting the user to login page, you can listen to $stateChangeError event which is fired in case the state is not resolved, and use $state.go to redirect the user.

url routing with parameter in angular js

I have this routing configuration in app.js:
$stateProvider.state('home', {
url: '/',
views:
{
'contentView':
{
templateUrl:'modules/login/login.html',
controller:'loginCtrl'
}
},
data:
{
login: true
}
});
Whenever user hits the browser with URL http://.../MyClient/#/?param=ParamValue.
It will take the user to the login page and I am able to access the param value as well.
There is a logout button in the successive pages and after logout, I want to redirect to the initial URL and if I try something like
$location.path('/#/?param=ParamValue');
the user will stay on the same page and URL will be like this:
http://.../MyClient/#/%23/%3Fparam=ParamValue
Please let me know how to fix this.
You should try to use the $state service to navigate between your states, $state.go('home', {param: ParamValue})
also add the parameter you need in the url template for the state.
$stateProvider.state('home', {
url: '/?param',
views:
{ ....
https://github.com/angular-ui/ui-router/wiki/Quick-Reference#state-1

Resources