AngularJS: need to fire event every time an ajax call is started - angularjs

I am trying to fire an event on $rootScope every time an ajax call is started.
var App = angular.module('MyApp');
App.config(function ($httpProvider) {
//add a transformRequest to preprocess request
$httpProvider.defaults.transformRequest.push(function () {
//resolving $rootScope manually since it's not possible to resolve instances in config blocks
var $rootScope = angular.injector(['ng']).get('$rootScope');
$rootScope.$broadcast('httpCallStarted');
var $log = angular.injector(['ng']).get('$log');
$log.log('httpCallStarted');
});
});
The event 'httpCallStarted' it's not being fired. I suspect that it's not correct to use $rootScope or any other instance service in config blocks. If so, how can I get an event everytime an http call is starting, without having to pass a config object in every time I am making a call?
Thanks in advance

You could always wrap $http in a service. Since services are only set up one time, you could just have the service factory set up the events for you. It feels a little hackish to me, honestly, but it's a good work around, since Angular doesn't have a global way to do this yet, unless something was added in 1.0.3 that I'm not aware of.
Here's a plunker of it working
And here's the code:
app.factory('httpPreConfig', ['$http', '$rootScope', function($http, $rootScope) {
$http.defaults.transformRequest.push(function (data) {
$rootScope.$broadcast('httpCallStarted');
return data;
});
$http.defaults.transformResponse.push(function(data){
$rootScope.$broadcast('httpCallStopped');
return data;
})
return $http;
}]);
app.controller('MainCtrl', function($scope, httpPreConfig) {
$scope.status = [];
$scope.$on('httpCallStarted', function(e) {
$scope.status.push('started');
});
$scope.$on('httpCallStopped', function(e) {
$scope.status.push('stopped');
});
$scope.sendGet = function (){
httpPreConfig.get('test.json');
};
});

Cagatay is right. Better use $http interceptors:
app.config(function ($httpProvider, $provide) {
$provide.factory('httpInterceptor', function ($q, $rootScope) {
return {
'request': function (config) {
// intercept and change config: e.g. change the URL
// config.url += '?nocache=' + (new Date()).getTime();
// broadcasting 'httpRequest' event
$rootScope.$broadcast('httpRequest', config);
return config || $q.when(config);
},
'response': function (response) {
// we can intercept and change response here...
// broadcasting 'httpResponse' event
$rootScope.$broadcast('httpResponse', response);
return response || $q.when(response);
},
'requestError': function (rejection) {
// broadcasting 'httpRequestError' event
$rootScope.$broadcast('httpRequestError', rejection);
return $q.reject(rejection);
},
'responseError': function (rejection) {
// broadcasting 'httpResponseError' event
$rootScope.$broadcast('httpResponseError', rejection);
return $q.reject(rejection);
}
};
});
$httpProvider.interceptors.push('httpInterceptor');
});
I think interceptors works for versions after 1.1.x.
There was responseInterceptors before that version.

I have verified that this code will work as you expect. As I mentioned above, you are not retrieving the injector that you think you are and need to retrieve the one being used for your app.
discussionApp.config(function($httpProvider) {
$httpProvider.defaults.transformRequest.push(function(data) {
var $injector, $log, $rootScope;
$injector = angular.element('#someid').injector();
$rootScope = $injector.get('$rootScope');
$rootScope.$broadcast('httpCallStarted');
$log = $injector.get('$log');
$log.log('httpCallStarted');
return data;
});
});

The best way to do this is to use an http interceptor. Check this link

Related

AngularJs, calling a $resource from within an Interceptor

I have just about given up with this. But I have a $resource that uses a query() to retrieve a list of items. I then have an Interceptor that runs over those items in order to insert an $interval.
The $interval at a specific point will then get an item again using the $resource's get() function. But its this get() that is not working.
The call happens but I cannot get the response back into the template.
myServices.factory('Items', ['$resource',
function($resource) {
return $resource("/items", {}, {
'query': {
interceptor: MyInterceptor,
url: "/items",
isArray: true
},
})
}]);
myServices.factory('MyInterceptor', function($q, $interval, $injector, $rootScope) {
return {
'response': function(response) {
angular.forEach(response.resource, function (item) {
$interval(function () {
item.something = 1 + item.something;
if(item.something == 10)
{
item = $injector.get("Mine").get({slug: item.id});
}
});
});
return response;
}
};
});
I thought that this line item = $injector.get("Mine").get({slug: item.id}); would work, but it doesn't. The new item is not changed in the template.
So I changed it to something like this, which did not work either;
$injector.get("Mine").get({slug: item.id}, function(data){
item = data;
});
I have tried with $q and $promise too, but I had no luck with those either. Finding decent examples on those subjects was tough too.
In short ...... I am using an Interceptor inside a $resource, with an $interval which then needs to eventually change a single value within an array of values within the $scope - how can I get this to work?
In short ...... I am using an Interceptor inside a $resource, with an $interval which then needs to eventually change a single value within an array of values within the $scope - how can I get this to work?
Based on the above statement I will give an answer.
First things first, remove the interceptor. You won't need it. Instead use a service.
Write a service called ProcessedItems.
angular.module('app')
.service('ProcessedItems', ['Items', '$q', function(Items, $q){
return {
query: function() {
var defer = $q.defer();
Items.query()
.$promise
.then(function(response){
angular.forEach(response.resource, function(i)){
i.s = 1 + i.s;
if(i.s == 10) {
i = $injector.get("Mine").get({slug: i.id});
i.$promise.then(function(){
defer.resolve(response);
}, function(){
defer.reject();
});
};
};
});
return defer.promise;
}
};
}]);
After this service is set up, in your controller you can do
angular.module('app')
.controller('AppController', ['$scope', 'ProcessedItems',
function($scope, ProcessedItems){
$scope.items = [];
ProcessedItems.query().then(function(pitems){
$scope.items = pitems;
});
});
What this will essentially do is first process the data completely and then display it in the view.

How to do error handling from .config block using httpProvider - angularjs

I would like to intercept all http error messages and give the user visual feedback. For this, I want to do something like this
.config(function($httpProvider) {
$httpProvider.interceptors.push(function($q) {
return {
'responseError': function(rejection) {
// show visual feedback or emit an event.
return $q.reject(rejection);
...
However, I can't inject $rootScope (to do a $rootScope.$emit()) since this is a config block. Nor is it possible to inject angular-growl or similar package, for the same reasons. How does one address this use case?
You can inject $rootScope together with $q:
.config(function($httpProvider) {
$httpProvider.interceptors.push(function($q, $rootScope) {
return {
'responseError': function(rejection) {
// show visual feedback or emit an event.
$rootScope.$emit( ... );
return $q.reject(rejection);
Try something like this
angular.module('interceptor.http', [])
.config(function ($httpProvider) {
$httpProvider.responseInterceptors.push('errorInterceptor');
})
.factory('errorInterceptor', function ($q, $rootScope) {
return function (promise) {
return promise.then(function (response) { },
function (response) {
$rootScope.$broadcast("error",{statusCode : 'error'});
return $q.reject(response);
});
};
});
... You can inject $rootScope. I don't know what you're talking about. However, if you'd like to inject dependencies at run-time that would otherwise require a service that would create a circular dependency, you can use the $injector service within config blocks like so:
// ... other angular code
.config(function($httpProvider){
$httpProvider.interceptors.push(function($q, $injector){
return {
responseError: function(arg1, arg2, argWhatever){
// This isn't run until there's actually an error (everything has already been configured)
// This is totally allowed
$http = $injector.get('$http');
// ... do whatever with your service
return $q.reject(/*something here*/);
}
};
});
});

How do I preform a redirect in an angular interceptor?

I am pretty new to angular, and I had a question on the best way to handle a redirect within an interceptor.
I have certain pages in my app that I should only be able to access if I have an account selected. So if an account is not selected I want the route the user to page to select the account.
The following is my failed attempt:
// within config
$httpProvider.interceptors.push(function($q, $injector){
return {
'request': function(config) {
var state = $injector.get('$state');
if(state.is('user.list')) {
var accountService = $injector.get('AccountService');
if(!accountService.accountSelected()){
// cancel the current request
var defer = $q.defer();
defer.resolve();
config.timeout = defer.promise;
state.go('account.select');
}
}
return config;
}
}
});
This is causing an infinite loop for me. For some reason when state.go fires -- and it gets re-intercepted the state is still "user.list"
Note: I am using ui-router, angular 1.2.6
Another Note: The other place I thought of putting this was in a state.resolve block.
do it like this
$injector.get('$state').transitionTo('public.login');
full code below
var interceptor = ['$location', '$q', '$injector', function($location, $q, $injector) {
function success(response) {
return response;
}
function error(response) {
if(response.status === 401) {
$injector.get('$state').transitionTo('public.login');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
return function(promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
I think you may want to try $location.path(destination) instead of $state.go(destination).
You'd better not put your redirect logic under 'request' block as accountService.accountSelected() may also make a request. here is the doc of $http interceptors
If you only have several routes which need to check account, it is better to put it in state.resolve block as you point out or put it in corresponding controller.

How can I get a service to access server data via an $http call?

I make an $http call inside a service that is supposed to get data from my server. For some reason I can't get my service to work - nothing happens. I know the server code works because if I place the $http call inside a function within my controller, then it gets the server data as expected. Here is the code I have so far:
app.service('myService', function($q,$compile,$http) {
this.getData = function() {
var deferred = $q.defer();
var promise = $http.get('myfile.php').success(function (data) {
var response = $compile(data)($scope);
deferred.resolve(response);
});
return deferred.promise;
};
});
Also, I know the code that uses this service works because if I do something like the following,
app.service('myService', function() {
this.getData = function() {
return 'TEST';
};
});
then I will see the word "TEST" show up in the div that utilizes this service. I feel like I'm very close, but there is something I am missing. Any ideas on what I'm doing wrong?
UPDATE:
controller: function($scope, $http, $rootScope, myService){
var scope = $rootScope;
scope.viewMyData = function() {
var element = myService.getData();
$('#myDiv').html(element);
}
}
HTML:
<div ng-click="viewMyData()">Click Here</div>
<div id="myDiv"></div>
If I strip the code in myService and simply return TEST (as above), then I will see "TEST" show up in id="myDiv". But for some reason the $http call isn't being triggered.
#tymeJV is right, but here's my attempt to spell out the example better. $http returns a promise interface that allows you to chain callbacks to be executed when the $http response returns. So, in this case, calling myService.getData() can't return the result immediately (it's off getting the data from the server), so you need to give it a function to execute when the server finally responds. So, with promises, you simply attach your callback using the thePromise.then(myCallbackFunc).
Service
app.service('myService', function($q,$compile,$http) {
this.getData = function() {
var promise = $http.get('myfile.php');
promise = promise.then(function (response) {
return response.data;
});
return promise;
};
});
Controller
controller: function($scope, $rootScope, myService){
var scope = $rootScope;
scope.viewMyData = function() {
myService.getData().then(function(data) {
$('#myDiv').html(element);
});
}
}
Use .then in the controller to continue the promise pattern:
myService.getData().then(function(data) {
$('#myDiv').html(data);
});

Abstracting $http calls into service

I'm wondering what the best way to abstract $http calls into an angularjs service is. I've done a bit of research and this seems to be the most common way:
app.factory('myService', function($http) {
return {
getFoo: function() {
return $http.get('foo.json').then(function(result) {
return result.data;
});
}
}
});
app.controller('MainCtrl', function($scope, myService) {
//the clean and simple way
$scope.foo = myService.getFoo();
}
But the problem with this approach is I can't figure out how to do anything on .error.
I'd prefer to have my .success and .error callbacks inside my controller.
Is there a way to abstract the http call inside the service whilst maintaining .error and .success callbacks inside the controller?
Thank you.
You can still use the on success/error calls.
The method you've highlighted returns a "Promise" object. A good thing about promises is that they are chainable.
So say you wish to respond to a $http request error in your controller:
app.factory('myService', function($http) {
return {
getFoo: function() {
return $http.get('foo.json').then(function(result) {
return result.data;
});
}
}
});
app.controller('MainCtrl', function($scope, myService) {
//the clean and simple way
$scope.foo = myService.getFoo().then(function(){
//Do something with successful response
}, function(){
//Do something with unsuccessful response
});
}
NOTE: This following next section no longer holds true. Promises used in templates are no longer automatically resolved to its values when the promise resolves.
You should also understand why assigning $scope.foo works in your templates. AngularJS has a bit of magic that will resolve any promises to the object you need in a template. So while your template might reference foo.bar and the output will be correct, whats actually happening in the background is that the template is waiting for the promise to be fulfilled before rendering that part of the template.
Also, another gotcha is to remember to return an rejected promise if you're handling the error somewhere up in the chain.
For example:
app.factory('myService', function($http, $q) {
return {
getFoo: function() {
return $http.get('foo.json').then(function(result) {
return result.data;
}, function(result){
//I'm doing something here to handle the error
return $q.reject(result);
});
}
}
});
app.controller('MainCtrl', function($scope, myService) {
//the clean and simple way
$scope.foo = myService.getFoo().then(function(){
//Do something with successful response
}, function(){
//Do something with unsuccessful response
});
}
If we didn't return a rejected promise in the service, the controller's 'success' code path will be run instead of the reject path.

Resources