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) {
...
Related
We have the following code in place for form validation:
$scope.masterModel.$save().then(function (data) {
$scope.masters.data.push(data);
$location.path('/master/edit/' + data.id);
}).error(function (data) {
$scope.errors = data.data;
});
Now we added code to generally catch code 500 server errors on a global level to the app.js
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(function ($injector) {
return {
'responseError': function (rejection) {
// do something on error
if (rejection.status === 500) {
var angularModalService= $injector.get("ModalService");
angularModalService.showModal({
templateUrl: "templates/common/session.html",
controller: "ModalController"
}).then(function (modal) {
modal.element.modal();
modal.close.then(function (result) {
if (result) {
}
});
});
}
}
};
});
}]);
As soon as we add this code, the error callback in the first code does not work anymore.
I think we have to somehow propagate the error in the responseError callback, but how does that work?
AngularJS Version is 1.5.11
You need to "reject the rejection" in the interceptor and return it in order for the error to be propagated:P
var app= angular.module('MyApp', []);
app.controller('Controller', function($scope, $http, $q) {
$http.get("http://www.example.invalid/fake.json")
.then(function(response) {
console.log("success");
}, function(error) {
console.log("controller error handler");
});
});
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(function ($injector, $q) {
return {
'responseError': function (rejection) {
console.log("interceptor error handler");
// do something on error
if (rejection.status === 500) {
// do something...
}
return $q.reject(rejection);
}
};
});
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApp">
<div ng-controller="Controller">
</div>
</div>
As you can see, I added the line
return $q.reject(rejection);
at the end of your interceptor. You can check the console and see that now both messages are logged.
My question is what is the best way to handle errors from http REST calls. Should I use interceptors or decorators? My rest functions look something like this:
queryFunction : function (config) {
var defer = $q.defer();
var config = {};
$http.get(someUrl, config) //or http.put, delete
.then(function (response) {
defer.resolve(response.data);
})
.catch(function (ex) {
defer.reject(ex);
});
return defer.promise;
},
How will the simplest interceptor look?
Here's the code for generic $http ErrorInterceptor:
app.factory('ErrorInterceptor', ['$q', function($q) {
return {
responseError: function(rejection) {
// An error message is shown for all kind of server errors
if (rejection.status == -1) {
//alert('Backend is not working');
//use rejection.data variable
}
return $q.reject(rejection);
}
};
}])
Then it can be included into app config
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('ErrorInterceptor');
})
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.
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")
}
}
}
}
]);
I am trying to call AuthenticationService.logout() on a 401 http error. However, I can't make it work. I suppose I can't inject a service to the config, but how can I achieve this then?
myApp.config(['$httpProvider', 'AuthenticationService', function ($httpProvider, AuthenticationService) {
var interceptor = ['$rootScope','$q', function(scope, $q) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) {
var deferred = $q.defer();
var req = {
config: response.config,
deferred: deferred
}
/* LOGOUT HERE */
AuthenticationService.logout();
return deferred.promise;
}
return $q.reject(response);
}
return function(promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
}]);
What am I doing wrong, and how can I fix it?