AngularJS interceptor not returning on success - angularjs

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

Related

How to multi-callback a promise in AngularJs with $q?

I'm using the code below in order to simplify the backend requests but I didn't catch how to call either a success method or an error method.
How can I reach the expected behavior commented in the code?
app.factory('REST', function ($http, $q, sweetAlert) {
return {
load: function (module, action, data) {
var deferred = $q.defer();
var promise = deferred.promise;
$http
.post('/api/'+module+'.php?action='+action, data)
.success(function (data) {
if(data.error)
{
sweetAlert.swal({
title: "Error",
text: data.error,
type: "warning"
});
//HERE I WANT TO CALL .error(details)
}
else
deferred.resolve(data.result);
}).error(function () {
//HERE I WANT TO CALL .error(details)
});
promise.success = function(fn) {
promise.then(fn);
return promise;
}
return promise;
}
};
});
This is the code which uses the code above:
$scope.login = function () {
$scope.loading = true;
var payload = {'credentials': $scope.logindata};
REST.load('access', 'login', payload).success(function(data) {
if(data.redirect)
$state.go(data.redirect);
$scope.loading = false;
}).error(function(data) { //THIS SHOULD BE CALLED
$scope.loading = false;
});
}
First of all, I strongly discourage you from attaching .success to the promise you are returning. This is not Promises/A-compliant, and its subtle difference from .then (as is implemented by $http) causes a lot of confusion. Just return a pure promise.
Other than that, a few things to note:
1) you don't need another $q.defer and deferred.resolve() - just chain to the original promise of $http and return the resulting promise. (see deferred anti-pattern)
2) to reject a promise - that is, to cause the .catch (not .error - see above about the subtle difference) to fire - you should return $q.reject().
All of the above produces the following:
app.factory('REST', function($http, $q, sweetAlert){
return {
load: function(module, action, data) {
// this "return" returns the promise of $http.then
return $http.post('/api/' + module + '.php?action=' + action, data)
.then(function(response) {
var data = response.data; // .then gets a response, unlike $http.success
if (data.error) {
sweetAlert.swal({
title: "Error",
text: data.error,
type: "warning"
});
//HERE I WANT TO CALL .error(details)
return $q.reject(data.error);
}
return data.result; // what you would have "resolved"
});
}
};
})
Then, as I said above, use the .then/.catch as you would with promises:
$scope.login = function () {
$scope.loading = true;
var payload = {'credentials': $scope.logindata};
REST.load('access', 'login', payload)
.then(function(data) {
if(data.redirect)
$state.go(data.redirect);
$scope.loading = false;
})
.catch(function(error) {
$scope.loading = false;
});
}
Update yr code as below
app.factory('REST', function ($http, $q, sweetAlert) {
return {
load: function (module, action, data) {
var deferred = $q.defer();
$http.post('/api/'+module+'.php?action='+action, data)
.success(function (data) {
if(data.error)
{
sweetAlert.swal({
title: "Error",
text: data.error,
type: "warning"
});
//HERE I WANT TO CALL .error(details)
deferred.reject(data.error);
}
else{
deferred.resolve(data.result);
}
})
.error(function (error) {
//HERE I WANT TO CALL .error(details)
deferred.reject(error);
});
return defferred.promise;
}
};
});
for yr controller
$scope.login = function () {
$scope.loading = true;
var payload = {'credentials': $scope.logindata};
REST.load('access', 'login', payload).then(
function(data) {
if(data.redirect)
$state.go(data.redirect);
$scope.loading = false;
},
function(error) {
$scope.loading = false;
});
}

reject promise when returning $http from service

I know I could do this:
angular.module('app.services').service('Resources', function ($q) {
this.ProductData = function() {
var deferred = $q.defer();
$http.get(url)
.then(function(result) {
deferred.resolve(result);
}, function() {
defered.reject("error");
});
return deferred.promise;
}
});
But could I avoid creating deferred obj, but still be able to do both resolve and reject?
angular.module('app.services').service('Resources', function () {
this.ProductData = function() {
return $http.get(url)
.then(function (result) {
// how would I do 'deferred.resolve or deferred.reject' here?
}, function () {
// how would I do 'deferred.reject' here?
});
}
});
You are right to avoid creating a deferred and another promise for $http.
If you need to reject the promise while the actual $http call has succeeded, you'd need to return $q.reject():
return $http.get(url)
.then(function(result){
if (result.data.length === 0) return $q.reject("error");
return result;
}

Angular response interceptor does not work for RestAngular

I have the following code:
!(function(window, angular){
'use strict';
angular.module('interceptor', ['settings']).
config(function($httpProvider, $injector){
var $http,
interceptor = ['$q', '$injector', function ($q, $injector) {
var error;
function success(response) {
var $http = $http || $injector.get('$http');
var $timeout = $timeout || $injector.get('$timeout');
var $rootScope = $rootScope || $injector.get('$rootScope');
var LOADER_CONF = LOADER_CONF || $injector.get('LOADER_CONF');
if($http.pendingRequests.length < 1) {
$timeout(function(){
if($http.pendingRequests.length < 1){
$rootScope._loading = false;
}
}, LOADER_CONF.SUSPEND_ON);
}
else{
$timeout(function(){
if($http.pendingRequests.length > 0){
$rootScope._loading = true;
}
}, LOADER_CONF.SUSPEND_OFF);
}
return response;
}
function error(response) {
var $state = $state || $injector.get("$state");
var $timeout = $timeout || $injector.get('$timeout');
var $rootScope = $rootScope || $injector.get('$rootScope');
var LOADER_CONF = LOADER_CONF || $injector.get('LOADER_CONF');
$timeout(function(){ $rootScope._loading = false, LOADER_CONF.SUSPEND_OFF});
return $q.reject(response);
}
return function (promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
});
})(window, window.angular);
a basic interceptor to toggle a spinner.
The interceptor works just fine for http requests that fetch partials, and internal angular stuff, but does not work for RestAngular requests (which is a wrapper for $http).
Any help is much appreciated.
it's possible Restangular interception isn't working because of promises. try using $q like so:
var interceptor = ['$rootScope', '$q', function ($rootScope, $q) {
return {
'request': function(config) {
console.log("request successful");
return config || $q.when(config);
},
'requestError': function(rejection) {
console.log("request error");
return $q.reject(rejection);
},
'response': function(response) {
console.log("response successful");
return response || $q.when(response);
},
'responseError' : function(rejection) {
console.log(rejection.status+" something bad happened");
$rootScope.errors = ["Unable to connect to server "+rejection.config.url];
return $q.reject(rejection);
}
};
}];

ui router if resolve fails why do load the view

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.

AngularJS - Calling service in config

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?

Resources