I am using angular-spinner to intercept all http request from my application and show a loading spinner.
Relevant code:-
//spinner configuration START
myApp.factory('spinnerInterceptor', ['usSpinnerService', function(usSpinnerService) {
return {
request: function(config) {
usSpinnerService.spin('spinner-1');
return config;
},
response:function(config){
usSpinnerService.stop('spinner-1');
return config;
},
responseError:function(config){
usSpinnerService.stop('spinner-1');
return config;
}
};
}]);
myApp.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('spinnerInterceptor');
}]);
//spinner configuration END
Except starting/stopping the spinner , I am simply just returning the config object.
Problem:-
One of my POST RESTful endpoint, return 404 status with an error message and still success handler of the $http block gets executed? Why?
$http({
method : 'POST',
url : url,
params :paramsJson,
data : _data
}).success(function(data,status) {
// THIS GET EXECUTED AFTER SPINNER INTERCEPTOR WITH STATUS 404 !!!!
}).error(function(data,status) {
// THIS BLOCK I EXPECT TO RUN IN CASE OF 404 or any non 2XX response
});
Before success/error handler of the $http, the spinnerInterceptor do get executed, which is somewhere playing with the error handling of the promise that gets returned.
When running my code without the spinner interceptor, everything works as expected.
Please help me fix this.
The problem here is "responseError" interceptor. you need to reject request in this block. else error is already handled in you interceptor so what ever you return here will go to success block.
The corrected interceptor code is:
responseError:function(config){//here we get response rename it
usSpinnerService.stop('spinner-1');
return $q.reject(config);
}
You can refer to Error Handling in angular for more info.
I had to develop an "automatic" spinner recently in a project. I used $q promises to signal success / error on the interceptor methods and it worked fine. My code was:
(function () {
'use strict';
function httpBusyInterceptor($q) {
function _responseError (response) {
//Hide spinner code
return $q.reject(response);
}
function _response (response) {
//Hide spinner code
return response || $q.when(response);
}
function _request (config) {
//Show spinner code
return config || $q.when(config);
}
return {
request: _request,
response: _response,
responseError: _responseError
};
}
httpBusyInterceptor.$inject = ['$q'];
angular.module('common.ui').factory('httpBusyInterceptor', httpBusyInterceptor);
})();
Related
I'm having a hardtime to create a Test with the Controller that uses promise when doing initialization. here's my angularjs scirpt.
Javascript.js
var appModule = angular.module('appModule', []);
appModule.controller('c0001Controller', function($http, $window) {
var user = {};
this.c0001Data = user;
this.submitForm = function() {
if(!angular.isUndefined(this.c0001Data.user_id) && !angular.isUndefined(this.c0001Data.password))
{
var promise = $http.post('C0001Login', user);
promise.success(function(data, status, headers, config) {
if(data.message == 'error'){
alert('Invalid Username/Password');
} else {
$window.location.href = data.url + "#/c0003";
}
});
promise.error(function(data, status, headers, config) {
alert("Invalid Username/Password");
});
}
else {
alert ("Invalid/ Username/password");
}
};
});
Using $httpBackend is more like setting up a fake call to intercept the original call of your $http service in your test cases.
Say you in your controller/service you have an $http get that gets from the request url 'api/employees'. In your test you would like to do something like this before the actual call to your function that calls $http:
$httpBackend.expectGET('api/employees').and.return(200, [
{ id: 1, name: 'sample' },
{ id: 2, name: 'sample 2' }
]);
(JasmineJS) In this way the original $http get request to your url 'api/employees' will not be called instead, the $httpBackend's setup/expected call will be called, and a http status code of 200 along with the array data will be returned.
This would work well on expecting a POST with data parameters. You should always know the request url, the data and other configs used in your original $http calls.
P.S. Always return appropriate HTTP status codes when using $httpBackend. Say, returning an HTTP status code 400 will trigger your catch block in the code you're testing.
I am using the $http get from Angular, I want to confirm that a http request is sent to our API. I am aware of the .success method but this waits for a return, is there a way to just get the $http object to confirm the request has been sent?
I used something similar to track number of requests sent, which are waiting for response.
Add an http-interceptor on app level:
.config(['$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('RequestCounterInterceptor');
}
])
The code of interceptor (you can modify it to handle just requests to your API's url):
.factory('RequestCounterInterceptor', ['RequestCounterService', '$q',
function(RequestCounterService, $q) {
return {
'request': function(request) {
RequestCounterService.increase();
return request;
},
'requestError': function(request) {
RequestCounterService.increase();
return $q.reject(request);
},
'response': function(response) {
RequestCounterService.decrease();
return response;
},
'responseError': function(response) {
RequestCounterService.decrease();
return $q.reject(response);
}
};
}
])
The service:
.service('RequestCounterService', ['$log',
function($log) {
var currentRequestCount = 0; // init
return {
increase: function() {
currentRequestCount++;
$log.log("currentRequestCount ", currentRequestCount);
},
decrease: function() {
currentRequestCount--;
$log.log("currentRequestCount ", currentRequestCount);
},
getCount: function() {
return currentRequestCount;
}
};
}
])
Then anywhere in you controller, directive... you can use RequestCounterService.getCount() to see how many requests have been sent and did not received response yet. You could display a message to user like There are currently XY requests being processed by the server or similar.
But in case the processing takes too much time, you should solve your issue server-side as James Gaunt proposed in comments.
My code makes many AngularJS $http requests. Often it is 3-4 at the same time.
Is there some way that I can intercept the http messages so that I get just one alert pop up if the internet connectivity is lost and there are multiple requests going on? I have seen other sites that do this but then if the action requires a number of http calls it seems that I could get more than one error popup coming.
If possible I would like to do this in just the one place in my code.
You need add responseInterceptors inside you $httpProvider in configuration phase of angular. This interceptor gets called after angular $httpProvider processing the response.
CODE
module.config(['$httpProvider', function($httpProvider) {
var interceptor = ['$rootScope', '$q', '$location', function(scope, $q, $location) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 500) {
alert("Internal Server Error")
return;
}
if (status == 404) {
alert("Page not found")
return;
}
// otherwise
return $q.reject(responseInterceptors);
}
return function(promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
}]);
Above code will provide you better control on error handling when any request fails.
For more details refer this anwser.
Update
For showing alert only once we could create a service,if error occurred then that will handle the set error variable
Service
module.service('errorService',function(){
//getter
this.getErrorFlag = function(){
return this.isError;
}
//setter
this.setErrorFlag = function(val){
this.isError = val;
}
});
Interceptor
module.config(['$httpProvider', function($httpProvider) {
var interceptor = ['$rootScope', '$q', '$location','errorService', function(scope, $q, $location,errorService) {
function success(response) {
return response;
}
function error(response) {
//setting error variable
errorService.setErrorFlag(true)
return $q.reject(responseInterceptors);
}
return function(promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
}]);
Inside you controller put all $http call promises inside $q.all, and when promises gets resolved then check for isError flag of errorService.
IsError flag will be true if error occurred at least one & by using it you can show error only once.
Controller
module.controller('appCtrl',['$scope','$q','errorService',function($scope,$q,errorService){
var ajax1 = $http.get('url').then(function(){
},
function(){
});
var ajax2 = $http.get('url').then(function(){
},
function(){
});
var ajax3 = $http.get('url').then(function(){
},
function(){
});
errorService.setErrorFlag(false);
$q.all(ajax1, ajax2, ajax3).then(function(data){
//check for isError flag from service
if(errorService.getErrorFlag())
alert('Error occurred while processing request.'); //this will show only once
else
alert('No error occurred while processing request.')
});
}]);
Hope this could help you.
Thanks.
If you don't want to use an interceptor, you could simply process the error callback of your $http calls:
$scope.httpError = null;
$scope.processHttpError = function() {
// If you don't already have got an http error
if (!$scope.httpError) {
$scope.httpError = "Cannot load stuff";
}
};
$http.get('/someUrl')
.success(function(data, status, headers, config) { ... })
.error($scope.processHttpError);
$http({
method: 'POST',
url: '/getAllUsers'
}).success(function(data, status, headers, config) {
if (data.length === 0) {
$location.path('/admin/login');
}
// process data
if (data.ok == 1) {
// correct data
} else {
// error
}
})
I use $http to fetch server data, and the server will respond an empty string if the auth fails. What I want to do is, if data is empty, then the application should redirect to login page. But since there are many controllers, and each controller has several $http call, so I don't want to copy and paste the directing code everywhere.
Is there any way I can "inject" this function on every $http response without writing the same code everywhere?
And am I able to apply it only on $http calls in some specific controllers?
You can use a http intercetor. The intercetor's response method is called right after $http receives the response from the backend. You can modify the response or make other actions. The method get called with the http response object and needs to return the response object directly, or as a promise containing the response or a new response object.
$provide.factory('myHttpInterceptor', function() {
return {
'response': function(response) {
if (/* response failed */ ) {
// $rootScope broadcast
// or $location login
// or whatever you want
}
return response;
}
};
});
$httpProvider.interceptors.push('myHttpInterceptor');
Edit:
I'm not sure if this is correct usage of $http config (I'never done this..) but you could also conditionally add a transformResponse function to a single $http call like this:
function transformErrorResponse(data, headersGetter, status) {
if (/* failed */)
// handle failure
}
$http({ transformResponse : transformErrorResponse})
.get(/* */)
.then(/* */);
You can create a factory definition and push it in config phase as Interceptor (using $httpprovider)
Factory Definition
angular.module('myApp').factory('httpInterceptor', ['$location', function($location) {
var httpInterceptor = {
response: function(config){
if (config.data.trim() == "") {
$location.path('\login');
}
return config;
}
};
return httpInterceptor;
}]);
Config Phase
angular.module('myApp').config(['$httpProvider' ,function($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
}]);
You can write a simple config for $http in your module config and write your error handling in the transformResponse property of the $http config.
Something like this:
angular.module("yourModule", []).config ($http) ->
$http({
transformResponse:(data, headersGetter, status)->
/**Handle your data empty stuff here. ***/
})
I have bug (or maybe wrong usage?) with ui-router, resolve, factory and $http.get call.
Here's a snippet of the code in the config section:
$stateProvider
.state('index', {
url: '/',
views: {
'': {
templateUrl: './views/layout.html',
controller: 'MyAppCtrl'
},
'app-navbar#index': {
templateUrl: './views/app-navbar.html'
},
'app-accordion#index': {
templateUrl: './views/app-accordion.html',
controller: 'AppController',
resolve: {
appPromiseObj: function (AppFactory) {
return AppFactory.getApps();
}
}
},
...
and have the following AppFactory
myApp.factory('AppFactory', function ($http) {
var appFac = {
apps: []
};
appFac.getApps = function () {
promiseObj = $http
.get('http://localhost:4567/applications')
.success(function (data) {
console.log("success calling http");
angular.copy(data, appFac.apps);
});
return promiseObj;
};
return appFac;
});
But when I run the app, the console.log message in the 'success' callback never gets executed. The browser console log shows the http call executes OK with code 200. I am assuming this means angular thinks it has failed or should I be doing something else?
I even tried returning the $q promise object (as suggested in other somewhat related stack overflow threads) but no success. In the factory code if I use test data (i.e., no HTTP call) everything works fine even if I don't return a promise object. Any pointer on where the problem could be? Appreciate any pointers to help me debug...
I created working plunker here. The problem was incorrect promise handling inside of the AppFactory.getApps(). We need to return the promise at the begining and then also return some adjusted stuff on success. Now it works...
This is the main change I made:
// INSTEAD of this
// appFac.getApps1 = function () {
// promiseObj = $http.get('http://localhost:4567/applications')
// .success(function (data) {
// console.log("success calling http");
// angular.copy(data, appFac.apps);
// });
//
// return promiseObj;
// Let's use this
appFac.getApps = function () {
return $http
.get('http://localhost:4567/applications')
.success(function (data) {
console.log("success calling http");
angular.copy(data, appFac.apps);
return appFac.apps
});
// this is already returned above
//return promiseObj;
Check it in action here
EXTEND
Based on your extended plunker (still not fully working as expected)
-http://plnkr.co/edit/c89j3eFvYyguMt0QznAI?p=preview
I created adjsuted and workin version
http://plnkr.co/edit/f2aucPcbtzqwIEogbjuJ?p=preview
The only changes was proper naming (e.g. app.js to be loaded as a script instead of script.js...). But at the end, the promise is now resolved and this json:
[{"id":10566982,"networkID":34256899,"appID":56114114
,"name":"10566982name","description"
...
]
Is loaded and converted into accordion:
56114114name
58616695name
Finally to answer your question in the comment below:
but what is the difference between promiseObj = $http(...)... ; return promiseObj and return $http (...); ?
There is no difference (except I see my approach a bit more clear). The real difference is:
angular.copy(data, appFac.apps);
vs
return appFac.apps
as a final statement of the .success() method. It MUST return something. tha's the trick