ui router if resolve fails why do load the view - angularjs

May be I miss something but
if I set a state like this:
.state('session_register', {
url: '/privatearea',
resolve: {
isLogged: function(Session){
return Session.isLogged();
}
},
templateUrl: 'private/user.html',
controller:'UserController'
})
if isLogged return a 401 status (resolve fails)
even if I don't really see the user.html in the browser
the partial is just loaded (firebug)
So I'm wondering
Is it the wanted behaviour ?
Is there a way to not load partial
when a resolve fails ?
I've got also an httpInterceptor
.factory('httpInterceptor', ['$q', '$location',
function($q, $location) {
return {
'response': function(response) {
if (response.status === 401) {
$location.path('/auth/login');
return $q.reject(response);
}
return response || $q.when(response);
},
'responseError': function(rejection) {
if (rejection.status === 401) {
$location.url('/auth/login');
return $q.reject(rejection);
}
return $q.reject(rejection);
}
};
}
])
//Http Interceptor to check auth failures for XHR requests
.config(['$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
}
]);
END UP
.factory('httpInterceptor', ['$q','$location',function ($q,$location) {
var canceller = $q.defer();
return {
'request': function(config) {
// promise that should abort the request when resolved.
config.timeout = canceller.promise;
return config;
},
'response': function(response) {
return response;
},
'responseError': function(rejection) {
if (rejection.status === 401) {
canceller.resolve('Unauthorized');
$location.url('/user/signin');
return $q.reject(rejection);
}
if (rejection.status === 403) {
canceller.resolve('Forbidden');
$location.url('/');
return $q.reject(rejection);
}
return $q.reject(rejection);
}
};
}
])
//Http Intercpetor to check auth failures for xhr requests
.config(['$httpProvider',function($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
}]);
it works :)

I think it is by design. Apart from an extra bandwidth, there is no need to block the template loading, otherwise the view will complete slower.
And there is no way to change this behavior too. From the source code:
https://github.com/angular-ui/ui-router/blob/0.2.10/src/state.js#L1158
dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
var promises = [ dst.resolve.then(function (globals) {
dst.globals = globals;
}) ];
if (inherited) promises.push(inherited);
// Resolve template and dependencies for all views.
forEach(state.views, function (view, name) {
var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
injectables.$template = [ function () {
return $view.load(name, { view: view, locals: locals, params: $stateParams, notify: false }) || '';
}];
promises.push($resolve.resolve(injectables, locals, dst.resolve, state).then(function (result) {
// References to the controller (only instantiated at link time)
if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
var injectLocals = angular.extend({}, injectables, locals);
result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
} else {
result.$$controller = view.controller;
}
// Provide access to the state itself for internal use
result.$$state = state;
result.$$controllerAs = view.controllerAs;
dst[name] = result;
}));
});
// Wait for all the promises and then return the activation object
return $q.all(promises).then(function (values) {
return dst;
});
A promise from state.resolve:
$resolve.resolve(state.resolve, locals, dst.resolve, state);
and $view.load (which make a http request for the templateUrl):
$view.load(name, { view: view, locals: locals, params: $stateParams, notify: false })
not wait for each other to finished first, it happens in pararell, and used in the return statement:
return $q.all(promises).then(function (values) {
Hope this clear things up.

Related

Define default action when $http returns certains status

Is it possible to set a default action for $http in my app?
The idea would be that if any $http call in a controller, or service of my app would return 405, I could change to state 'login'
Something like this (of course this will not work at all, but I guess that shows the idea)
angular.module('app',[]).config(function($http){
$http.error(function(error,state){
if (state===405){
do stuff
}
);
You can use interceptors to achieve this.
// register the interceptor as a service
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
return {
// optional method
'request': function(config) {
// do something on success
return config;
},
// optional method
'requestError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
},
// optional method
'response': function(response) {
// do something on success
return response;
},
// optional method
'responseError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
}
};
});
$httpProvider.interceptors.push('myHttpInterceptor');
// alternatively, register the interceptor via an anonymous factory
$httpProvider.interceptors.push(function($q, dependency1, dependency2) {
return {
'request': function(config) {
// same as above
},
'response': function(response) {
// same as above
}
};
});
Read more about it here
You should do something like this:
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
//Do whatever you want on success
}, function errorCallback(response) {
if(response.status == 405) {
//Go to login
}
});
Below code might help you:
$http.get("url")
.success(function (data, status, headers, config) {
$scope.content = data;
if (data == '405') {
$state.go('login', {id: the_necesarry_id});
}
else {window.alert(' ');}
})
.error(function (data, status, headers, config) {
});
use angularjs Interceptors
// interceptors.js
.factory('httpInterceptor', function ($q, $injector) {
return {
'requestError': function(rejection) {
// do something on error
if(rejection.status === 405){
$injector.get('$state').transitionTo('login');
}
return $q.reject(rejection);
},
'responseError': function(rejection) {
// do something on error
if(rejection.status === 405){
$injector.get('$state').transitionTo('login');
}
return $q.reject(rejection);
}
};
});
// app.js
app.config( function ($httpProvider, $interpolateProvider, $routeProvider) {
$httpProvider.interceptors.push('httpInterceptor');
...
});

Prevent a state change in ui-router based on HTTP query

How do I prevent a state change for a specific "to" state in ui-router (is it using onEnter?) assuming I have this route:
.state('auth.confirm', {
url: '/confirm/:resetToken',
template: '<confirm-page></confirm-page>',
data: { pageTitle: 'Confirm Reset', specialClass: 'gray-bg' }
})
and this service with this promise-based function:
validateResetToken: function(resetToken) {
var self = this;
var deferred = $q.defer();
$http.post(AppConstants.hostRootUrl + '/auth/reset/validate', { resetToken: resetToken })
.then(function(response) {
if(response.data && response.data.success) {
// if we got a 200 return and it indicates success in the response, resolve
self.message = 'Success';
deferred.resolve(self.message);
}
else if (response.data && !response.data.success && response.data.error) {
// if we got a 200 return, but success is falsey and there's an error message, reject with that message
self.message = response.data.error;
deferred.reject(self.message);
}
else {
// error with generic message
self.message = 'Unknown response. Contact administrator.';
deferred.reject(self.message);
}
}, function(errPost) {
if (errPost.data && errPost.data.error) {
self.message = errPost.data.error;
deferred.reject(self.message);
}
else {
self.message = 'Could not connect.';
deferred.reject(self.message);
}
});
return deferred.promise;
},
For posterity (and Googlers) sake, Alon Eitan made me take a second look at my resolve approach, and I realized that my addition of the catch() was causing the rejected promise to not percolate up. This final code works:
.state('auth.confirm', {
url: '/confirm/:resetToken',
template: '<confirm-page></confirm-page>',
data: { pageTitle: 'Confirm Reset', specialClass: 'gray-bg' },
resolve: {
validated: function($q, $stateParams, AuthService, toastr) {
//$log.log('auth.confirm resolve $stateParams',$stateParams);
return AuthService.validateResetToken($stateParams.resetToken).catch(function(validateErr) {
toastr.error(validateErr, 'Blocked', {closeButton: true});
return $q.reject(validateErr);
});
}
}
})
You can create a rule as in https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-create-rules-to-prevent-access-to-a-state
Adapt their example:
app.config(function($stateProvider) {
$stateProvider.state('privatePage', {
data: {
rule: function(token) {
return validateResetToken(token)
}
});
});
app.run(function($rootScope, $state, $stateParams) {
$rootScope.$on('$stateChangeStart', function(e, to) {
if (!angular.isFunction(to.data.rule)) return;
var result = to.data.rule($stateParams.resetToken);
if (result && result.to) {
e.preventDefault();
// Optionally set option.notify to false if you don't want
// to retrigger another $stateChangeStart event
$state.go(result.to, result.params, {notify: false});
}
});
});

AngularJS interceptor not returning on success

I'm trying to add a popup with retry option as follows so that the user can click on that when lose the connection during a HTTP call.
angular.module('app.services', [])
.factory('httpResponseErrorInterceptor', ['$injector', '$q', function($injector, $q) {
return {
'responseError': function(response) {
var $ionicPopup = $injector.get('$ionicPopup');
if (response.status === 0) {
var confirmPopup = $ionicPopup.confirm({
title: 'No Connectivity!',
template: 'Internet not available'
});
confirmPopup.then(function(res) {
if(res) {
var $http = $injector.get('$http');
return $http(response.config);
} else {
return $q.reject(response);
}
});
}
}
};
}])
It is receiving the response from the http call, but not returning the response to the calling point. In the same way I tried the following code,
.factory('httpResponseErrorInterceptor', ['$injector', '$q', function($injector, $q) {
return {
'responseError': function(response) {
if (response.status === 0) {
var $http = $injector.get('$http');
return $http(response.config);
}
return $q.reject(response);
}
};
}])
But this one is returning the response properly to the calling point when we get the connection back. I'm not sure where I'm going wrong in the first code.
Any help/idea would be appreciated.
You should return confirmPopup.then() call.
Do like this:
return confirmPopup.then(function(res) {
if(res) {
var $http = $injector.get('$http');
return $http(response.config);
} else {
return $q.reject(response);
}
});
Example of chaining:
var promise = confirmPopup.then(function(res) {
if(res) {
var $http = $injector.get('$http');
return $http(response.config);
} else {
return $q.reject(response);
}
});
promise.then(function(success){
//HTTP SUCCESS
}, function(error){
//HTTP ERROR OR REJECT RESPONSE
});
Based on Patrick Kelleter's answer, I framed this working solution,
.factory('httpResponseErrorInterceptor', ['$injector', '$q', function($injector, $q) {
return {
'responseError': function(response) {
var $ionicPopup = $injector.get('$ionicPopup');
var $ionicLoading = $injector.get('$ionicLoading');
$ionicLoading.hide();
if (response.status === 0) {
var userInputDefer = $q.defer();
var confirmPopup = $ionicPopup.confirm({
title: 'No Connectivity!',
template: 'Internet not available',
okText: 'Retry'
});
confirmPopup.then(function(res) {
if(res) {
var $http = $injector.get('$http');
userInputDefer.resolve($http(response.config));
} else {
userInputDefer.reject($q.reject(response));
}
});
return userInputDefer.promise;
}
}
};
}]);
Edit:
Just for future reference for someone, for using the above HTTP interceptor, you have to include the factory in config as follows,
.config(['$httpProvider',function($httpProvider) {
$httpProvider.interceptors.push('httpResponseErrorInterceptor');
}]);
confirmPopup.then(function(res) {
if(res) {
var $http = $injector.get('$http');
return $http(response.config);
} else {
return $q.reject(response);
}
});
here is the problem. you are returning stuff ($http / $q) in the asynchronous callback of the confirmPopup.
the confirmPopup is async and you define a callback via ".then".
whatever you are returning there will not reach your calling point. it is the return value of the callback. which probably will not land anywhere (depending on the implementation of confirmPopup, but i doubt that it expects you to return anything there)
you will have to use your own promise and return it synchronously at the end of your callback

Issue getting $rootScope injected into config of Angular?

I am doing:
myApp.config([
'$httpProvider', '$rootScope', function($httpProvider, $rootScope) {
$httpProvider.interceptors.push(function($q) {
return {
responseError: function(rejection) {
var defer;
defer = $q.defer();
if (rejection.status === 401) {
$rootScope.$broadcast('api-error', rejection);
}
defer.reject(rejection);
return defer.promise;
},
response: function(response) {
var defer;
defer = $q.defer();
console.log(response);
if (response.status === 401) {
$rootScope.$broadcast('api-error', response);
defer.reject(response);
} else {
defer.resolve(response);
}
return defer.promise;
}
};
});
}
]);
But I get an error that it cannot find $rootScope. I read this answer and it says to do some weird global variable stuff, which seems like a bad idea.
So how can I redirect to another route upon a 401 status?
Inject $rootScope in the interceptor, not in the configuration function:
myApp.config([
'$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(function($q, $rootScope) {
...

AngularJS Interceptor to Redirect

ExpressJS is sending the following response...
res.send('ItemUploaded');
I'm trying to get AngularJS to see this response via an Interceptor and perform a redirect. Does anyone have sample code where Angular catches a server response (such as my "ItemUploaded") and performs a redirect to a partial (via $location)?
This works fine. I have used it in my application.
var interceptor = function ($q, $location) {
return {
request: function (config) {//req
console.log(config);
return config;
},
response: function (result) {//res
console.log('Repos:');
console.log(result.status);
return result;
},
responseError: function (rejection) {//error
console.log('Failed with', rejection.status, 'status');
if (rejection.status == 403) {
$location.url('/dashboard');
}
return $q.reject(rejection);
}
}
};
module.config(function ($httpProvider) {
$httpProvider.interceptors.push(interceptor);
});
Here is the factory for the interceptor:
.factory('InterceptorService',['$q', '$location', function( $q, $location, $http){
var InterceptorServiceFactory = {};
var _request = function(config){
//success logic here
return config;
}
var _responseError = function(rejection) {
//error here. for example server respond with 401
return $q.reject(rejection);
}
InterceptorServiceFactory.request = _request;
InterceptorServiceFactory.responseError = _responseError;
return InterceptorServiceFactory;
}]);
then register the interceptor:
.config(["$httpProvider", function ($httpProvider) {
$httpProvider.interceptors.push('InterceptorService');
}]);
Every request coming will be passed here.
You can implement a interceptor factory which will redirect if it gets a matching result.
angular
.module('app')
.factory("httpinterceptor", ["$location",
function(location) {
return {
'response': function(response) {
if (response.data === "ItemUploaded") {
location.path("/ItemUploaded")
}
}
}
}
]);

Resources