I am using AngularJS and ui-router and have the following code which catches 404's and loads in a 404 template:
$urlRouterProvider.otherwise(function($injector, $location){
$injector.invoke(['$state', function($state) {
$state.go('404');
}]);
});
It keeps the URL intact instead of redirecting, but shows the 404 template:
.state('404',
{
views: {
'body': {
templateUrl: 'partials/404.html',
}
}
});
Normally it would redirect to the root state with: $urlRouterProvider.otherwise('/');
However when HTML5 mode is turned off and the user visits the root URL e.g. http://www.domain.com/app/ it loads the 404 state instead of redirecting to http://www.domain.com/app/#/
How can I keep the 404 state code above, but have it redirect to the hashbang when accessing the initial page? So effectively only have the 404 load if the request is anything other than the home page?
I can't use the $stateNotFound or $stateChangeSuccess and need a way to check if it's a home page request within the actual config setup itself that can toggle between the / and the state.404 within the otherwise statement.
So something along the lines of (which does seem to work).
$urlRouterProvider.otherwise(function($injector, $location){
$injector.invoke(['$state', function($state) {
if( $location.$$path == '')
$state.go('home'); // redirect to /#/
else
$state.go('404'); // else go to 404 state
}]);
});
But is there a better way to handle this?
Use a httpErrorResponseInterceptor! This will get fired when a httpResponse happens. Here is an example
angular
.module('myApp')
.factory('httpErrorResponseInterceptor', ['$q', '$state', function ($q, $state) {
return {
responseError: function error(response) {
switch (response.status) {
case 404:
$state.go('404');
break;
default:
console.log("Unhandled HTTP error:", response);
}
return $q.reject(response);
}
};
}
]);
Related
I have an issue with the browser back button on our Angularjs application. Here's the basic setup. We multiple pages (really states...) that perform a validation of the token on the client computer. Here's an example of one such state:
theApp.controller('someController',
['$scope', '$http', '$state', '$stateParams', '$log', 'authenticationFactory','myFactory',
function ($scope, $http, $state, $stateParams, log, authenticationFactory, myFactory) {
log.debug('someController');
authenticationFactory.validateToken()
.then(success => {
return filesFactory.getFileContentDetails($stateParams.fileId);
})
.then(response => {
$scope.stuff = response;
})
.catch(error => {
$state.go('login', { error });
})
.finally(() => {
spinnerFactory.hide('loading');
});
}]);
Here is a snippet of the defined login state:
$stateProvider
.state('login', {
url: '/login',
templateUrl: 'pages/login/login.html',
controller: 'loginController',
params: {
error: null
}
}).state('someresource', {
url: '/someresource/:fileId',
templateUrl: 'pages/someresource/someresource.html',
controller: 'someController'
});
And, here is some of the login controller:
theApp.controller('loginController',
['$scope', '$state', '$log', 'authenticationFactory', 'statusPopupFactory',
function ($scope, $state, log, authenticationFactory, statusPopupFactory) {
log.debug('loginController');
if ($state.params.error) {
log.error($state.params.error.data);
statusPopupFactory.setStatus('danger', $state.params.error.data);
} else if (localStorage.token) {
authenticationFactory.validateToken()
.then(user => {
$state.go('home');
})
.catch(error => {
log.error(`Token validation error: ${error.data}`);
});
}
}]);
So as it is, everything behaves as it should... can login, states transition, etc. You can hit URL directly which would bring up the state controlled by someController, rather than going through the normal app menuing system (ie, I could just navigate to https://theApp/someresource/1, which is run by someController).
When this is done, and they do not have a token (or it's expired or whatever other reason...), they get bumped to the state login. At this point, if they hit the browser back, the browser is trying to redirect them to the previous state in which they came from, rather than the website they came in from. When it tries to go to the previous state, it merely attempts to do revalidation of token, that fails, and just bounces them back to login once again. So it's a cyclical loop.
The issue is that from the "login" page at this point, they can't actually navigate back to the previous website. It feels like they are "trapped" in the site, never to get out of the login page unless you simply manually change the URL or close tab, etc.
How can I go about solving this?
My initial thoughts were that when the login state loads, I could somehow look at all the browser history, and remove all previous states for the application, but preserve any states that are not from the same host url. That way from login, they could "browser back" just fine. However I've exhausted lots of searching in trying to figure this out and... thus here I am.
I want to refresh my entire page in the success function like this.
What i am trying to do is in the success function i am redirecting to /login and reloading the page.
if (results.data == 'success') {
$location.path('/login');
$location.reload();
}
I am redirected to /login but the page isn't refreshing. It throws error in the console like TypeError: $location.reload is not a function.
How can i fix this and reload the page once after $location.path('/login'); ?
Note : I am doing this in my controller
Did you inject the $location service in your controller?
You can also try $route.reload(), be sure to inject this service as well.
window.location.assign("/login")
You have to config your $routeProvider
mainApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
when('/login', {
templateUrl: 'login.html',
controller: "loginController"
})
});
Then you can redirect the path using
$location.path('login');
anywhere in your controller.
Try with:
$route.reload();
And make sure to add "$route" when declaring the dependencies of your Controller.
try this location.reload(true)
Try this:
window.location.assign("http://localhost/testlogin.php");
you can try this..
if (results.data == 'success') {
$location.path('/login');
echo "<script>location.reload();</script>";
}
In my AngularJS application I want to catch errors and send the user to a 404 state like so:
$rootScope.$on('$stateNotFound', function(event, unfoundState, fromState, fromParams){
//console.log(unfoundState.to);
//console.log(unfoundState.toParams);
//console.log(unfoundState.options);
$state.go('404');
});
This is for when a user clicks a bad link (e.g. has no state, as ones with states but no content are handled by the otherwise method) in the app, etc.
The code loads the 404 state fine:
.state('404',
{
views: {
'body': {
templateUrl: 'partials/404.html',
}
}
});
But what I want to do is change the URL to the state they tried to access, so basically load the 404 state but use the incorrect URL (as a 404 would work in a server environment).
I've looked into doing:
history.replaceState(null, null, unfoundState.to);
$state.go('404');
But that causes major errors in the app and changes the URL but not the state!
How can I do this?
Try this to catch all URLs not matching any of your previous routes :
.state("404", {
url: "*path", // will catch all URLs
templateUrl: 'partials/404.html'
})
According to the docs, I think you can do:
$state.go('404',{},{location:false});
Note: I'm in the process of creating a plunkr to verify but I don't have time to finish it up completely right now
Some of my AngularJS routes are to pages which require the user to be authenticated with my API. In those cases, I'd like the user to be redirected to the login page so they can authenticate. For example, if a guest accesses /account/settings, they should be redirected to the login form.
From brainstorming I came up with listening for the $locationChangeStart event and if it's a location which requires authentication then redirect the user to the login form. I can do that simple enough in my applications run() event:
.run(['$rootScope', function($rootScope) {
$rootScope.$on('$locationChangeStart', function(event) {
// Decide if this location required an authenticated user and redirect appropriately
});
}]);
The next step is keeping a list of all my applications routes that require authentication, so I tried adding them as parameters to my $routeProvider:
$routeProvider.when('/account/settings', {templateUrl: '/partials/account/settings.html', controller: 'AccountSettingCtrl', requiresAuthentication: true});
But I don't see any way to get the requiresAuthentication key from within the $locationChangeStart event.
Am I overthinking this? I tried to find a way for Angular to do this natively but couldn't find anything.
What I did is implement an angularjs interceptor which handles http request errors. Basically, what I do is when I get the 401 (unauthorized) from my backend, I save the current url and redirect the user to a login page. When the user does login successfully, I retrieve the saved url to set the path with $location.
app.config(function ($routeProvider, $locationProvider, $httpProvider) {
/* Global interceptor for 401 - not authorized */
var interceptor = ['$location', '$q', 'authorizationService', function ($location, $q, authorizationService) {
function success(response) {
return response;
}
function error(response) {
if (response.status === 401) {
authorizationService.saveUrl($location.path());
$location.path('/logon');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
return function (promise) {
return promise.then(success, error);
};
} ];
});
In the logon controller (on successful logon) I set the location like this:
$location.path(authorizationService.getUrl());
I use angular-http-auth in my project. If you have a backend is easier to delegate on it to handle the authentication, showing a login form automatically in each 401 response, than duplicate the authentication mapping both on client and server.
I'm trying to sort out a login system using cookies so that the user's login will persist after leaving the app. I am able to set the cookie correctly, but I am unclear as to how I can use the stored cookie to limit the users access to the login screen if they are already logged in.
I think the best way to do this would be within the routes. This is what my file currently looks like:
var routes = angular.module('we365', ['rcForm', 'ngCookie', 'ngCookies']);
routes.config(function ($routeProvider) {
$routeProvider
.when('/login', {
templateUrl: 'views/login.html',
controller: 'loginCtrl'
})
.when('/', {// get digest view
templateUrl: 'views/getDigest.html',
controller: 'GetDigestCtrl'
})
.when('/artifact/:artifact_id', {// single artifact view
templateUrl: 'views/artifact.html',
controller: 'artifactCtrl'
})
.otherwise({
redirectTo: '/'
});
});
Also, i'd like to hide the 'login' button from the parent view so that the user isn't able to click on it. This is what the view looks like now:
<div class="container">
<div class="page-header col col-lg-12">
<h1>Welcome!</h1>
Login
Load Digest Data
</div>
</div>
There are many ways, I have two that are my favorite:
1) Check on route change
angular.module('MyApp', [])
.run(function($rootScope, myLoginService) {
$rootScope.$on('$routeChangeStart', function () {
if (!myLoginService.isUserLoggedIn()) {
$location.path('/login');
}
});
You can replace the isUserLogged for a mapper service that receives where the user wants to go; if the user has the proper privileges (either stored within a cookie or local storage in the form of a token), then let the route succeed. Otherwise, show an error, or route him to wherever you want. In my case, the myLoginService checks for a localStorage.
2) Any data request to a server has a token included to headers; failed requests (401) are intercepted and stored, while the user is redirected
This one is more for CRUD apps and not necessarily for routing, but the concept is simple: a user A can perform N actions as long as he/she has the privileges to do so; if he tries to perform an action (or M actions) that he's not allowed, then the request is intercepted and queued in order to ask him to authenticate with a user that CAN do those actions
.factory('securityInterceptor', ['$injector', 'securityRetryQueue', function($injector, queue) {
return function(promise) {
// Intercept failed requests
return promise.then(null, function(originalResponse) {
if(originalResponse.status === 401) {
// The request bounced because it was not authorized - add a new request to the retry queue
promise = queue.pushRetryFn('unauthorized-server', function retryRequest() {
// We must use $injector to get the $http service to prevent circular dependency
return $injector.get('$http')(originalResponse.config);
});
}
return promise;
});
};
}]);
Again, this is for more "data like" requests and not necessarily for routing. This was stolen from the AngularJS sample app. You should check it for more examples.