In AngularJS, I wish to create a catch-all-be-all ajax loader that does not need to be weaved into each controller in order to work. Traditionally, in jQuery I can do something like this:
(function globalAjaxLoader($){
"use strict";
var ajaxBoundElements = [$posts, $navigationLinks];
ajaxBoundElements.forEach(function($elm){
$elm.on('click', function(){
$loader.show();
});
$(document).ajaxSuccess(function (event, XMLHttpRequest, ajaxOptions){
$loader.hide();
});
})(jQuery);
However, in AngularJS I am not seeing a global way of detecting ajaxCompletion (that is without going through the promise returned for each ajax call made through Angular individually)?
Thanks.
Here I've put together a jsBin showing how to do this with an http interceptor.
I've used $rootScope.loadingCount so you can actually ng-show and ng-hide your based on that. Here is an example of the markup (you'd obviously use something a bit different:
<h1 ng-show="loadingCount > 0">Loading...</h1>
And here is the javascript:
angular
.module('app', [])
.config(httpInterceptorConfig)
.factory('loadingDialogInterceptor', loadingDialogInterceptor);
// create your interceptor
loadingDialogInterceptor.$inject = ['$q', '$rootScope'];
function loadingDialogInterceptor($q, $rootScope) {
$rootScope.loadingCount = 0;
function showLoading() {
$rootScope.loadingCount++;
}
function hideLoading() {
if ($rootScope.loadingCount > 0) {
$rootScope.loadingCount--;
}
}
return {
request: function (config) {
showLoading();
return config || $q.when(config);
},
response: function(response) {
hideLoading();
return response || $q.when(response);
},
responseError: function(response) {
hideLoading();
return $q.reject(response);
}
};
}
// actually register your interceptor
httpInterceptorConfig.$inject = ['$httpProvider'];
function httpInterceptorConfig($httpProvider) {
$httpProvider.interceptors.push('loadingDialogInterceptor');
}
You can achieve the same with interceptors.
myModule
.factory('httpResponseInterceptor', [
'$q',
function ($q) {
return {
request: function (request) {
$loader.show();
return request;
},
requestError: function () {
$loader.hide();
},
response: function (response) {
$loader.hide();
return response || $q.when(response);
},
responseError: function (rejection) {
$loader.hide();
return $q.reject(rejection);
}
}
}
])
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('httpResponseInterceptor');
$httpProvider.defaults.transformRequest.push(function (data) {
$loader.show();
return data;
});
})
Related
I need to trigger an event when all of my $http calls are processed. I also need to know if any call has failed. I tried using the available solutions on stackoverflow such as using an interceptor.
angular.module('app').factory('httpInterceptor', ['$q', '$rootScope',
function ($q, $rootScope) {
var loadingCount = 0;
return {
request: function (config) {
if(++loadingCount === 1) {
$rootScope.$broadcast('loading:progress');
}
return config || $q.when(config);
},
response: function (response) {
if(--loadingCount === 0) {
$rootScope.$broadcast('loading:finish');
}
return response || $q.when(response);
},
responseError: function (response) {
if(--loadingCount === 0) {
$rootScope.$broadcast('loading:finish');
}
return $q.reject(response);
}
};
}
]).config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
}]);
However, with this approach the $rootScope.$broadcast('loading:finish') gets called after every $http call is completed. I want to trigger an event when all the $http calls are over.
I cannot use $q since the $http calls in my page belong to various directives and are not called in the same controller.
You can use below code to check the number of pending requests for $http. I am using this to show loading spinner in my project.
$http.pendingRequests.length
For keeping a track of failed and success calls you can use something like this:
angular.module('myApp', [])
.run(function ($rootScope){
$rootScope.failedCalls = 0;
$rootScope.successCalls = 0;
})
.controller('MyCtrl',
function($log, $scope, myService) {
$scope.getMyListing = function(employee) {
var promise =
myService.getEmployeeDetails('employees');
promise.then(
function(payload) {
$scope.listingData = payload.data;
$rootScope.successCalls++; //Counter for success calls
},
function(errorPayload) {
$log.error('failure loading employee details', errorPayload);
$rootScope.failedCalls++; //Counter for failed calls
});
};
})
.factory('myService', function($http) {
return {
getEmployeeDetails: function(id) {
return $http.get('/api/v1/employees/' + id);
}
}
});
Basically I have created 2 root scope variables and using it as counters to maintain count of success and failed calls which you can use wherever you want.
I want to move this function to services.js:
News.all().async().then(function(data) {
$scope.news = data['data']['NewsList'];
});
And than call it in controller.js by this command:
$scope.news = News.all();
I try many ways, but them did not work.
Here is my services.js:
.factory('News', function($http) {
function returnNews() {
return {
async: function() {
return $http.get('test.json');
}
};
}
return {
all: function() {
return returnNews();
}
}
});
Well if you call News.all() in the end you will get an object with an async property: {async} and I don't think this is what you want. What you can do is pass a callback to the service:
.factory('News', function($http) {
return {
all: function(callback) {
return $http.get('test.json').then(callback);
}
}
});
and in the controller you have to do:
News.all(function(data){
$scope.news = data
});
In a controller, I need to retrieve the status of a segment. The segments are loaded from an API using $resource.
In the resource, segmentsresource.js I have:
angular.module('appApp')
.factory('SegmentsResource', function ($resource) {
return $resource('http://localhost:3000/api/v1/segments/:id');
});
In the service, segmentsservice.js I have:
angular.module('appApp')
.service('SegmentsService', function (SegmentsResource) {
this.getSegmentStatus = function(segmentId) {
return SegmentsResource.get({
id: segmentId
}, function(segment) {
return segment.status;
})
};
});
I'm trying to do return segment.status so I can use the result ('available', 'translated', etc) in the controller, main.js:
$scope.checkAndEditSegment = function(segmentId) {
SegmentsService.getSegmentStatus(segmentId)
.then(function(status) {
if (status === 'available') {
console.log('segment is available');
}
});
};
However, this doesn't work. Console spits: TypeError: Cannot read property 'then' of undefined', so I have a problem with promises.
How to fix this?
But why you are taking this long route when you can simply call the factory from controller.
angular.module('appApp')
.factory('SegmentsResource', function ($resource) {
return $resource('http://localhost:3000/api/v1/segments/:id');
});
$scope.checkAndEditSegment = function(segmentId) {
SegmentsResource.get(segmentId)
.then(function(status) {
if (status === 'available') {
console.log('segment is available');
}
});
};
I resolved using $q:
In the service:
this.getSegmentStatus = function(segmentId) {
var dfd = $q.defer();
SegmentsResource.get({ id: segmentId }, function(segment) {
dfd.resolve(segment.status);
})
return dfd.promise;
};
In the controller:
SegmentsService.getSegmentStatus(segmentId)
.then(function(status) {
console.log(status);
});
How can I wait with my function till the $http request is finished?
My services.js looks as follows:
var app = angular.module('starter.services', []);
app.factory('Deals', function($http) {
function getDeals() {
$http.get('http://www.domain.com/library/fct.get_deals.php')
.success(function (data) {
var deals = data;
return deals;
})
.error(function(err){
});
}
return {
all: function() {
return getDeals();
},
get: function(keyID) {
//...
}
}
});
My controllers.js looks like:
var app = angular.module('starter.controllers', []);
app.controller('DealCtrl', function($scope, Deals) {
$scope.deals = Deals.all();
console.log($scope.deals);
});
The console.log in my controllers.js file outputs "undefined", but when I output the deals in the getDeals() function it contains the correct array which I get from my server.
What am I doing wrong?
$http and all of the async services in angularjs return a promise object. See promise api.
You need to use then method to assign it to a value in the scope.
So your controller:
app.controller('DealCtrl', function($scope, Deals) {
Deals.all().then(function (deals) {
$scope.deals = deals;
console.log($scope.deals);
});
});
Your service
app.factory('Deals', function($http) {
function getDeals() {
return $http.get('http://www.domain.com/library/fct.get_deals.php')
.success(function (data) {
var deals = data;
return deals;
});
}
return {
all: function() {
return getDeals();
},
get: function(keyID) {
//...
}
}
});
When my website was 100% jQuery, I used to do this:
$.ajaxSetup({
global: true,
error: function(xhr, status, err) {
if (xhr.status == 401) {
window.location = "./index.html";
}
}
});
to set a global handler for 401 errors. Now, I use angularjs with $resource and $http to do my (REST) requests to the server. Is there any way to similarly set a global error handler with angular?
I'm also building a website with angular and I came across this same obstacle for global 401 handling. I ended up using http interceptor when I came across this blog post. Maybe you'll find it as helpful as I did.
"Authentication in AngularJS (or similar) based application.", espeo software
EDIT: final solution
angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives'], function ($routeProvider, $locationProvider, $httpProvider) {
var interceptor = ['$rootScope', '$q', function (scope, $q) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) {
window.location = "./index.html";
return;
}
// otherwise
return $q.reject(response);
}
return function (promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
Please note that responseInterceptors have been deprecated with Angular 1.1.4.
Below you can find an excerpt based on the official docs, showing the new way to implement interceptors.
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
return {
'response': function(response) {
// do something on success
return response || $q.when(response);
},
'responseError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise;
}
return $q.reject(rejection);
}
};
});
$httpProvider.interceptors.push('myHttpInterceptor');
This is how it looks in my project using Coffeescript:
angular.module("globalErrors", ['appStateModule']).factory "myHttpInterceptor", ($q, $log, growl) ->
response: (response) ->
$log.debug "success with status #{response.status}"
response || $q.when response
responseError: (rejection) ->
$log.debug "error with status #{rejection.status} and data: #{rejection.data['message']}"
switch rejection.status
when 403
growl.addErrorMessage "You don't have the right to do this"
when 0
growl.addErrorMessage "No connection, internet is down?"
else
growl.addErrorMessage "#{rejection.data['message']}"
# do something on error
$q.reject rejection
.config ($provide, $httpProvider) ->
$httpProvider.interceptors.push('myHttpInterceptor')
Create the file <script type="text/javascript" src="../js/config/httpInterceptor.js" ></script> with this content:
(function(){
var httpInterceptor = function ($provide, $httpProvider) {
$provide.factory('httpInterceptor', function ($q) {
return {
response: function (response) {
return response || $q.when(response);
},
responseError: function (rejection) {
if(rejection.status === 401) {
// you are not autorized
}
return $q.reject(rejection);
}
};
});
$httpProvider.interceptors.push('httpInterceptor');
};
angular.module("myModule").config(httpInterceptor);
}());