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.
Related
I'm trying to pass a params object to the $http.get() service. My params look like this:
var params = {
one: value,
two: value
}
And I'm trying to pass them into my function like so:
$http.get('/someUrl', params)
.success(function(data) {
// stuff
})
.error(function(data) {
// error stuff
});
Is this the correct way to go about doing this?
The second argument of $http is a config object (see documentation). Amongst other properties, the config object accepts a params property:
params – {Object.<string|Object>} – Map of strings or objects which will be serialized with the paramSerializer and appended as GET parameters.
Therefore you have to pass the parameters as such
var config = {
params: {
one: value,
two: value
}
}
$http.get('/someUrl', config).then(...)
Suppose the values for the parameters are respectively '1' and '2', $http will send a GET request to the following url:
/someUrl?one=1&two=2
As a side note, try to avoid using success and error functions on $http. They have been deprecated as of angular 1.4.4. Use the methods then with a success and an error callback instead, or then with only a success callback and catch.
Service/Factory
For the actual call use a factory or service that you can inject to the controllers you need it in. This is an example factory passing parameters
.factory('Chats', function ($http, $rootScope, $stateParams) {
return {
all: function () {
return $http.get('http://ip_address_or_url:3000/chats', { params: { user_id: $rootScope.session } })
}
};
});
Controller
In your controller you use the service like this
.controller('ChatsCtrl', function ($scope, Chats) {
Chats.all().success(function (response) {
$scope.chats = response;
})
})
I have faced similar issue in recent time and I had to add few additional details to request (I used accepted answer with some headers):
$http.get(url, {
params: {
paramOne: valueOne,
paramTwo: valueTwo,
...
},
headers: {
'key': 'value'
},
// responseType was required in my case as I was basically
// retrieving PDf document using this REST endpoint
// This is not required in your case,
// keeping it for somebody else's reference
responseType: 'arraybuffer'
}).success(
function(data, status, headers, config) {
// do some stuff here with data
}).error(function(data) {
// do some stuff here with data
});
The $http documentation suggest that the second argument to the $http.get method is an object which you can pass with it "param" object.
Try something like this:
$http.get('/someUrl', {params: params})
.success(function(data) {
// stuff
})
.error(function(data) {
// error stuff
});
First project in AngularJS and I started creating my services (factories) that I made modular like this
angular.module('app.services.public', [])
.factory('publicService', ['$http', function publicService($http) {
var results = {};
results.contact = function (name, email, message){
return $http.get();
};
return results;
}]);
That I then call in my main angular app by including it. When I call it, I need to listen for success or error
publicService.contact().success(callback).error(callback)
My question is, I'm going to be doing a lot of API requests through these services and seems to be bad code to listen to the error everytime since 90% of the time it will do the same thing.
How can I create a wrapper around the $http.get or around all factory calls?
So something like
apiCall = function (url, data, successCallback, errorCallback){
$http.get(url,data).success(function(){
successCallback()
}).error(function(){
if(errorCallback()){ errorCallback(); return; }
// or display general error message
})
}
I would recommend against converting promise-based into callback-based APIs. Angular adopted promises and it best to stay with them.
Also, stay away from $http-specific .success/.error and use promise .then/.catch APIs.
How wide do you need to cast your net to handle $http errors?
1) Say, it only applies to your publicService service, then you can "handle" it at the each function:
.factory("publicService", function($http, $q){
function handleError(){
// invokes error handlers
}
return {
onError: function(cb){
// register error handlers
},
doSomethingA: function(){
return $http.get("some/url/A")
.then(function(response){
return response.data;
})
.catch(function(error){
handleError(error);
return $q.reject(error); // still "rethrow" the error
}
},
doSomethingB: function(){
// similar to above
},
// etc...
};
})
Then you could separate request from error handling:
.controller("MainCtrl", function($scope, publicService){
publicService.onError(function(error){
$scope.showError = true; // or something like that
})
})
.controller("FunctionACtrl", function($scope, publicService){
publicService.doSomethingA()
.then(function(data){
$scope.data = data;
});
})
2) Of course, the above, would only apply to request made via publicService. If you want to catch all $http errors, you could implement an $http interceptors. I won't go into detail - there is enough info in documentation and elsewhere - but it would could work like below:
.factory("ErrorService", function(){
return {
onError: function(cb){
// register error handlers
},
broadcastError: function(error){
// invoke error handlers
}
};
})
Then in interceptor, use ErrorService as a dependency:
'responseError': function(rejection) {
ErrorService.broadcastError(rejection);
return $q.reject(rejection);
}
Then you could handle the errors globally:
.controller("MainCtrl", function($scope, ErrorService){
ErrorService.onError(function(error){
$scope.showError = true; // or something like that
})
})
You have the right idea. You can do it easily with a Factory.
myApp.factory(APIService, function(publicService, $http) {
return {
// create methods in here
...
contact: function(cb) {
$http.get(url,data).success(cb).error(function(err){
console.error('oh no!', err);
});
}
};
});
Then you can use it in your controllers.
APIService.contact(function(data){
console.log('response from the api!', data);
});
You can even move your error handler to its own factory as well.
I would suggest an implementation using angular's $q service.
angular.module('app.services.public', [])
.factory('publicService', ['$http', '$q', function publicService($http, $q) {
var results = {};
results.contact = function (name, email, message){
return $q.when($http.get());
};
return results;
}]);
Or rather than use the $q.when(...) method you can use $q.deferred like so:
angular.module('app.services.public', [])
.factory('publicService', ['$http', '$q', function publicService($http, $q) {
var deferred = $q.deferred();
var results = {};
results.contact = function (name, email, message){
$http.get().success(function(data){
deferred.resolve({
// assumes data retried from http request has a title and price attribute
title: data.title,
cost: data.price});
}).error(function(data){
deferred.reject(data);
});
};
return deferred.promise;
}]);
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. ***/
})