Abstracting $http calls into service - angularjs

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.

Related

Angular async service

I'm trying to do an ajax call via $http service inside a custom service. Then I want to customize, inside my controller, the data received in the custom service.
I wrap the customizing data function within $interval inside the controller: by this way I can customize my data when it is received.
The problem is: while the data is correctly logged in the service, the service seems like it doesn't return anything, although it should have returned (return response.data.Items)
So $interval loops indefinitely, and I can't customize my data.
var myApp = angular.module('MyApp', []);
myApp.service('ajaxcall', ['$http', function($http) {
this.getjson = function () {
$http.get("http://localhost:3000/one")
.then(function(response) {
console.log(response.data.Items); //data logged correctly
return response.data.Items;
});
}
}])
.controller('MyCtrl', ['$scope', '$interval', 'ajaxcall', function($scope, $interval, ajaxcall) {
var new_items = ajaxcall.getjson();
customize_data = $interval(function () { //loops indefintely, new_items=undefined, why?
if (new_items) {
// customize data
}
}, 200);
for(var i=0; i<new_items.length; i++) {
$scope.items.push(new_items[i]);
}
}]);
You could say: just move the customize data function in the custom service. First at all I don't want to do it. Secondly it doesn't even make sense: the $scope is not available in a service, so in any case I should wait for the $http reply.
There were several things which I wanted to point out there.
Do return $http promise from this.getjson method, so that you can chain that promise while getting data from it.
this.getjson = function() {
//returned promise here
return $http.get("http://localhost:3000/one")
.then(function(response) {
console.log(response.data.Items); //data logged correctly
return response.data.Items;
});
}
var new_items = ajaxcall.getjson() line doesn't stored the data returned by getjson call, it will have undefined value as your currently getting. After finish up above change, new_items will hold promise return by ajaxcall.getjson. Thereafter use $q.when to keep eye on promise to get resolved & check for data inside its .then function.
customize_data = $interval(function() { //loops indefintely, new_items=undefined, why?
$q.when(new_items).then(function(res) {
if (customizeData) {
//Do Logic
}
})
}, 200);
Side Note: You could face problem with this code, as you had 200ms time for each interval . Which can make multiple ajax calls before completing the last call(which would be kind of unexpected behaviour). To
resolve such issue you could use $interval.cancel(customize_data);
//once desired interval work has done
if you want to get the return data, you would write a factory instead of service!
code:
myApp.factory('ajaxcall', ['$http', function($http) {
return {
getjson : function () {
$http.get("http://localhost:3000/one")
.then(function(response) {
console.log(response.data.Items); //data logged correctly
return response.data.Items;
});
}
}
}])
You are misusing the promise returned by your asynchronous call. Here is what you need to do in your controller to change the data:
ajaxcall.getjson()
.then(function(new_items) {
// customize data
// this part should go here as well
for(var i=0; i<new_items.length; i++) {
$scope.items.push(new_items[i]);
}
});
No need to use intervals or timeouts. Pay attention, ajaxcall.getjson() does NOT return your data, it returns the promise resolved with your items.
Read about the promises in angular.
use promise when your making http calls from service
myApp.service('ajaxcall', ['$http', '$q', function($http, $q) {
this.getjson = function() {
var q = $q.defer();
$http.get("http://localhost:3000/one")
.success(function(data) {
console.log(data); //data logged correctly
q.resolve(data);
})
.error(function(err) {
q.reject(err);
});
return q.promise;
}
}]);
changes in controller to wait for promise
ajaxcall.getjson()
.then(function(data){
console.log(data);
});

Execute code within a factory when needed, not when loaded into controller

Factory:
.factory("myFac", ['$http', '$q', function($http, $q) {
var defer = $q.defer();
$http.get('some/sample/url').then(function (response) { //success
/*
* Do something with response that needs to be complete before
* controller code is executed.
*/
defer.resolve('done');
}, function() { //error
defer.reject();
});
return defer.promise;
}]);
Controller:
.controller("testCtrl", ['$scope', 'myFac', function($scope, myFac) {
/*
* Factory code above is executed immediately as 'myFac' is loaded into controller.
* I do not want this.
*/
if($scope.someArbitraryBool === true) {
//THIS is when I want to execute code within myFac
myFac.then(function () {
//Execute code that is dependent on myFac resolving
});
}
}]);
Please let me know if it is possible to delay the code in the factory until I need it. Also, if there's a better way to execute on this concept, feel free to correct.
You factory has $http.get directly inside factory function which return custom $q promise. So while you inject the factory dependency inside your controller function, it ask angular to create an object of myFac factory function, while creating object of function it executes the code block which you have returned your factory, basically which returns promise.
What you could do is, just return a object {} from the factory function which will have method name with its definition, so when you inject inside angular controller it will return service object, which will various method like getData method. And whenever you want to call the method of factory you could do factoryName.methodName() like myFac.getData().
Also you have created a extra promise inside your service which is not needed in first place, as you can utilize the promise of $http.get (which returns a promise object.)
Factory
.factory("myFac", ['$http', function($http) {
var getData = return $http.get('some/sample/url').then(function (response) { //success
return 'done'; //return to resolve promise with data.
}, function(error) { //error
return 'error'; //return to reject promise with data.
});
return {
getData: getData
};
}]);
Controller
.controller("testCtrl", ['$scope', 'myFac', function($scope, myFac) {
if($scope.someArbitraryBool === true) {
//Here you could call the get data method.
myFac.getData().then(function () {
//Execute code that is dependent on myFac resolving
});
}
}]);

update a service variable within an $http callback

I'm using a service to make user data available to various controllers in my Angular app. I'm stuck trying to figure out how to use the $http service to update a variable local to the service (in my case "this.users"). I've tried with and without promises. The server is responding correctly.
I've read several excellent articles for how to use $http within a service to update the scope of a controller. The best being this one: http://sravi-kiran.blogspot.com/2013/03/MovingAjaxCallsToACustomServiceInAngularJS.html. That does not help me though because it negates the benefits of using a service. Mainly, modifying the scope in one controller does not modify throughout the rest of the app.
Here is what I have thus far.
app.service('UserService', ['$http', function($http) {
this.users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
// this.users is undefined here
console.log(this.users);
}
};
promise.then(function() {
// this.users is undefined here
console.log('this.users');
});
}]);
Any help is greatly appreciated. Thank you.
Try using
var users = [];
rather than
this.users = [];
and see what
console.log(users);
outputs in each of those cases.
Your service is oddly defined, but if you have a return in it you can access it from any controller:
app.service('UserService', ['$http', function($http) {
var users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
// this.users is undefined here
console.log(users);
users = data.data;
}
};
return {
getUsers: function(){
return users;
}
}
}]);
so in your controller, you can use:
var myUsers = UserService.getUsers();
UPDATE to use a service correctly here, your service should return a promise and the promise should be accessed in the controller: Here's an example from another answer I gave
// your service should return a promise
app.service('PickerService', [$http', function($http) {
return {
getFiles: function(){
return $http.get('files.json'); // this returns a promise, the promise is not executed here
}
}
}]);
then in your controller do this:
PickerService.getFiles().then(function(returnValues){ // the promise is executed here as the return values are here
$scope.myDirectiveData = returnValues.data;
});
this does not have scope anymore where you are trying to use it do this instead:
app.service('UserService', [$http', function($http) {
var users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
console.log(users);
}
};
promise.then(function() {
console.log(users);
});
}]);
all local variables to a service should just be vars if you assign them to this as a property than they will be included every time the service is injected into a controller which is bad practice.
I think what your asking for is a solution along the lines of defining your service like this:
angular.module('app')
.service('User', function($http, $q) {
var users = null;
var deferred = $q.defer()
return {
getUsers: function() {
if(users) {
deferred.resolve(users);
} else {
$http.get('users.json');
.success(function(result) {
deferred.resolve(result);
})
.error(function(error) {
deferred.reject(error);
});
}
return deferred.promise;
}
};
});
Then in one Each controller you would have to do this:
angular.module('app')
.controller('ACtrl', function($scope, User) {
User.getUsers().then(function(users) {
// Same object that's in BCtrl
$scope.users = users;
});
});
angular.module('app')
.controller('BCtrl', function($scope, User) {
User.getUsers().then(function(users) {
// Same object that's in ACtrl
$scope.users = users;
});
});
NOTE: Because the deferred.promise the same promise passed to all controllers, executing deferred.resolve(users) in the future will cause all then success callbacks in each of your controllers to be called essentially overwriting the old users list.
All operations on the list will be noticed in all controllers because the users array is a shared object at that point. This will only handle updates to the user list/each individual user on the client side of your application. If you want to persist changes to the server, you're going to have to add other $http methods to your service to handle CRUD operations on a user. This can generally be tricky and I highly advise that you check out ngResource, which takes care of basic RESTful operations

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);
});

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

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

Resources