angular factory .success and .error functions - javascript

I an new to angular and I am wondering what is the best practice for the .success and .error functions to be placed, within the controller or within the factory? example:
do I use this:
(function(){
'use strict';
angular
.factory('apiService', function($http){
var apiService = {
getProfileData: getProfileData
}
return apiService;
function getProfileData(url){
return $http.jsonp(url);
}
});
})();
or this:
(function(){
'use strict';
angular
.factory('apiService', function($http){
var apiService = {
getProfileData: getProfileData
}
return apiService;
function getProfileData(url){
return $http.jsonp(url)
.success(function(data){
return data;
})
.error(function(err){
return err;
});
}
});
})();
and how should I be handling this in a controller?

You should definitely choose the 2nd option you posted and handle it in the service. Services are meant to be reusable, so if error handling is not done in there, it will have to be done in every single controller that consumes it.
EDIT:
The exception to this rule would be if the error and success handling would be done completely different depending on the controller that consumes the service, but I have never found this to be the case for me.

I would advise having it in the service/factory. Controller doesn't need to know about the communication implementation (which you might want to mock in the future).
Separation of concerns. :)

Both the approaches are correct, but depends on how you wanted to expose the API.
1st approach
It is pretty simple. You should go for this approach only if you are giving whole control over all the aspect available in the $http response like data, header, status & config
2nd approach
By using this approach you could create an abstraction layer between your callee method & service, It would return data from the service, other part of response would hidden for the callee method.Also here you could do some data manipulation operation Some times you could also validation before returning a data & you could resolve or reject promise based on condition.
I'd suggest you to go for 2nd approach so that you could get more access on data before returning it to callee method.

You should choose 2nd option, but .success return a promise but offeres slightly more convienient syntax, where as .then is full power of the promise API but slightly more verbose, so despite of using .success i suggest you to prefer .then.

Related

Calling $http promise inside controller

Thanks in advance for the help...
I have a controller I'm using to call an API to send a password reset link. I have abstracted the actual $http call off into a service to keep the controller thin. Originally I was doing something like this:
angular.module('module')
.service('forgotPasswordService', ['$http', function($http) {
$http(request).then(function() {
return {}; //return some object using response
}]);
I felt like this would be the best approach as it would keep the controller as thin as possible and kept all service related actions separate. My problem with this was that returning from within the promise never actually returned me anything. I had to actually return the $http service call to get the promise. The only way I was able to make all of this work was if I called .then from within the controller.
//controller
angular.module('module')
.controller('forgotPasswordCtrl', ['forgotPasswordService', function(forgotPasswordService) {
forgotPasswordService.forgotPassword(emailAddress).then(function() {
//do stuff
}
}]);
//service
angular.module('module')
.service('forgotPasswordService', ['$http', function($http){
this.forgotPassword = function(emailAddress) {
return $http(request);
};
}]);
This just feels a little wrong to me because the controller now depends on receiving a promise back from the service. I may just be overthinking this but I would like a second opinion.
Is this considered acceptable/good practice? Is there an alternative to this which would allow me to achieve the encapsulation I'm looking for?
Thanks again.
I've interfaced with the $http from the controller in two slightly different ways.
Like you it felt wrong returning the $http from the service and interfacing with it.
So first I created services and passed in a success method and an error method (callbacks).
// Service
angular.module('module')
.service('forgotPasswordService', ['$http', function($http) {
function sendForgotPasswordEmail(emailAddress, success, error){
$http.post('/api/v1/resetpassword', {emailAddress:emailAddress})
.then(success, error);
}
return {
sendForgotPasswordEmail: sendForgotPasswordEmail
}
}]);
// Controller
angular.module('module')
.controller('forgotPasswordCtrl', ['forgotPasswordService', function(forgotPasswordService) {
forgotPasswordService.sendForgotPasswordEmail(emailAddress,
function(response){ //success
// notify user of success
},
function(response){ // error
// notify user of error
});
}]);
This worked great. I created an large application this way, but as I started on my second large angular project I wondered why I was hiding the $http's promise?
By passing back the promise, I can use other libraries that support promises. With my first approach I can't leverage other libraries promise support.
Passing back the $http promise
// Service
angular.module('module')
.service('forgotPasswordService', ['$http', function($http) {
function sendForgotPasswordEmail(emailAddress){
return $http.post('/api/v1/resetpassword', {emailAddress:emailAddress});
}
return {
sendForgotPasswordEmail: sendForgotPasswordEmail
}
}]);
// Controller
angular.module('module')
.controller('forgotPasswordCtrl', ['forgotPasswordService', function(forgotPasswordService) {
forgotPasswordService.sendForgotPasswordEmail(emailAddress)
.then(
function(response){ //success
// notify user of success
},
function(response){ // error
// notify user of error
});
}]);
I deleted my original answer, and I feel like a dork for stating that you could do it the other way. When I went back and checked my original code back when I first started angular, I found that I was calling then() twice in my application - once in my service where I returned the data, and once in my controller because calling $http(request).then() returns a promise.
The fact is, you're dealing with an asynchronous call. Suppose in your controller, you wanted to do this:
$scope.foo = myService.getFoo(); // No then()
The XmlHttpRequest inside the $http in getFoo() is an asynchronous call, meaning that it calls it and moves on. The synchronous option is deprecated. It's bad practice to make a blocking synchronous HTTP call because it will seize up your UI. This means you should use a callback when the data is ready, and the promise API is made just for that.
If you absolutely do not want to use the then() in your controller, I suppose you could probably pass your scope binding parameters to your service and let your service update them in your then call. I haven't tried this, and because it's asynchronous, I'm not sure if angular will know to call a digest() so you may need to call a $scope.$apply() if the values don't update. I don't like this, because I think the control of the values in the scope should be handled by the controller and not the service, but again - it's your personal preference.
Sorry for leading you astray with my initial answer - I ran into the same question you had, but when I looked back - I saw I used a silly solution.
-- Relevant statements in original answer --
Consider the following:
Where do you want your error handling for the call and who needs to know about it?
Do you need to handle specific failures in a particular controller or can they all be grouped together to one error handler? For some apps, I like to display the errors in a particular place rather than in a general modal dialog, but it's acceptable to create a service to handle all errors and pop them up for the user.
Where do you want to handle your progress/busy indicator?
If you have an interceptor wired up for all http calls and broadcasting an event to show/hide the busy indicator, then you don't need to worry about handling the promise in the controller. However some directives will use the promise to show a busy indicator which requires you to bind it to the scope in the controller.
To me, the decision is determined by the requirements and by personal choice.
Try using a callback like so:
angular.module('module')
.service('forgotPasswordService', ['$http', function($http) {
var recovery = function(request, callback) {
$http(request).then(function(response) {
callback(response);
})
}
return { recovery: recovery }
}]);
Then you would call it like this:
forgotPasswordService.recovery('http://...', function(response){
console.log(response);
})

AngularJS get dashboard settings using $htttp before initializing a dashboard controller

So I am still on a crash course with Angular. I am working on quite a complicated dashboard framework, all written in angular. Before I load the controllers, I need to get a bunch of dashboard settings from the server first using $HTTP. These settings are then used to control the layout of the dashboards.
So I read the way angular builds is by first running config methods, then run methods, then the controllers.
I can't use $HTTP in a config method, so I have built this in my main.js:
MetronicApp.run(['$rootScope','$http', function($rootScope,$http) {
var CUID = Cookies("CUID");
console.log('portlet settings for '+ CUID);
$http.get('/myurl/V3_portlet_settings?p_user_id='+CUID)
.then(function(response) {
console.log(response.data);
console.log('portlet status: ' + response.status);
$rootScope.$broadcast("dashSettings",response.data);
});
}]);
When I run, this all works happily and I see the data in the console.
Then in my controller:
$scope.$on( "dashSettings",
function(event,data){
$scope.dData = data;
console.log('dash data service identified in dash controller');
console.log($scope.dData.count);
} );
Couple of questions:
Is this the best way to get settings before initializing the dash. My plan would be to embed the calls that build the dash inside my $scope.$on block. I started looking at how to run a run method synchronously before the controllers initialize, but maybe I don't need to.
Any obvious idea why the $scope.$on method does not seem to fire?
Thanks in advance
A different approach would be to place your $http functions in a service or factory and then resolve these in your controller.
The key here is the use of promise. Angular documentation describes this as
A service that helps you run functions asynchronously, and use their
return values (or exceptions) when they are done processing
First create a service:
app.factory('DataService', function($http) {
var getValues= function() {
var url = '/myurl/V3_portlet_settings?p_user_id='+ CUID;
return $http.jsonp(url) // returns a promise
};
return {
getValues: getValues
}
});
And then in your controller:
myApp.controller('MyController', function ($scope, DataService) {
DataService.getValues().then( // resolve the promise using .then()
function(data){
// successcallback
// you can now safely populate the data in you controller
console.log(data);
},
function(error){
// errorcallback
console.log(error);
})
});
I think it is a better approach to use data services to handle data operations such as $http requests.
The return of promises allows for chaining of (multiple) promises and better handling of async calls.
You might find John Papa's style guide useful, especially the section about 'Separate Data Calls' (Y060) and 'Return a Promise from Data Calls' (Y061)

JavaScript nested promise scoping and AngularJS

I have a real problem with JavaScript promises that I've been trying to solve for the last few hours and I just can't seem to fix it. My experience with promises is limited so I'm open to the idea that my approach is simply incorrect.
Right now I'm building an app that requires a two-step process:
Connect to an external PaaS service, which returns a promise
Within that promise, retrieve some data
Here's a sample of a factory I created:
app.factory('serviceFactory', [
function() {
var getData = function getData() {
service.connect(apiKey).then(function() {
service.getData('dataStore').then(function(result) {
// Retrieve data
return result;
}, errorFunction);
},
errorFunction);
};
return {
getData: getData
};
}
]);
As you can see, there are nested promises here. What's causing me problems is when I try to use the data from the most deeply-nested promise within an AngularJS view. Specifically, I want to use the data from that promise in an ng-repeat statement. But no matter what I try, it just won't show up. I've attempted to assign data within the promise instead of returning, like so:
service.getData('dataStore').then(function(result) {
// Retrieve data
// Assigned the enclosing scope's this to 'self'
self.data = result;
}, errorFunction);
That doesn't work either. I've tried a variety of other approaches, but I just can't seem to get that data to the view. There's no problem getting it to show up in a console.log(data)call, so I know the data is coming back correctly. Does anyone have experience solving a problem like this?
I would suggest that you'll try to avoid nested promises. You can take a look at this blog post, which will let you see how you can avoid 'promise soup' and have promise chaining instead.
As for your question, I would recommend the following:
A quick solution will be to fix your problem. You are returning the factory method wrong:
app.factory('serviceFactory', [
function() {
var getData = function getData() {
return service.connect(apiKey).then(function() {
service.getData('dataStore').then(function(result) {
// Retrieve data
return result;
}, errorFunction);
},
errorFunction);
};//here you should close the 'getData method
return {
getData: getData
};
}
]);
But, you can refactor your code to chain your promises. Something like:
app.factory('serviceFactory', [
function() {
var connect = function connect() {
return service.connect(apiKey);
};
var getData = function getData(data) {
return service.getData(data);
};
return {
getData: getData,
connect: connect
};
}
]);
Now, you can do something like this:
serviceFactory.connect(apiKey)
.then(serviceFactory.getData)
.then(function(result){
//use data here
})
All of this should be tested - you can add a plunker or jsbin if you want a working solution...
EDIT
I think that you have another problem here. You are mixing between serviceFactory and service. I'm not sure that I understand if this is the same service, or which is who. Can you provide a more detailed code or add plunker/jsbin etc.
I've edited this answer, which I originally deleted because I didn't explain what I meant very clearly and it garnered some downvotes (without explanation, but that's my guess). Anyway, here is a more complete answer.
I suspect that your problem is that whatever PaaS you are using has no awareness of Angular, and Angular likewise has no awareness of the PaaS. You say in your question that the PaaS has methods that return promises, but if Angular is not aware of those promises, then, when the promises resolve, Angular does not know to update the DOM. Angular does this via the digest cycle which is where Angular checks everything that it is watching to see if it has changed. When using $q (or other Angular services like $http), Angular knows to automatically kick off a digest cycle when they resolve. It does not, however, kick off a digest cycle when promises created by other means resolve.
This is what I think is happening in your code. Your PaaS is giving you promises, which are resolving properly (you said you can see the results via console), but your HTML is not being updated.
I modified the plunkr we were working on to demonstrate this in action. I created a mock PaaS (not knowing what you are using) that creates promises using jQuery and resolves them. As you can see, when the promises resolve, the result is logged to the console, but the DOM is not resolved.
angular.module("app",[])
.value("mockPaaS", mockPaaS)
.factory("dataFactory", function($q, mockPaaS){
function getData(){
return mockPaaS.connect()
.then(mockPaaS.getData);
}
return {
getData: getData
}
})
.controller("DataController", function (dataFactory) {
var vm = this;
dataFactory.getData().then(function(result){
console.log(result);
vm.dataArr = result;
});
})
.directive("myApp", function(){
return {
bindToController: true,
controller: "DataController",
controllerAs: 'myApp',
template: "<div ng-repeat='i in myApp.dataArr'>{{i}}</div>"
};
});
I was originally suggesting that you could solve this problem by adding a $scope.$apply() after you capture the result of the promise. I've forked the Plunker and you can see here it does, in fact update the DOM.
.controller("DataController", function ($scope, dataFactory) {
var vm = this;
dataFactory.getData().then(function(result){
console.log(result);
vm.dataArr = result;
$scope.$apply();
});
})
There is, however, a more idiomatic solution. When you get a promise from outside angular that you need to use in Angular, you can wrap that promise using $q.when (an Angular aware promise), and when the external promise resolves, Angular should kick off it's digest cycle naturally.
.factory("dataFactory", function($q, mockPaaS){
function getData(){
return $q.when(mockPaaS.connect()
.then(mockPaaS.getData));
}
return {
getData: getData
}
})
.controller("DataController", function (dataFactory) {
var vm = this;
dataFactory.getData().then(function(result){
console.log(result);
vm.dataArr = result;
});
})
Ben Nadel gives a nice explanation of this issue here.

AngularJs : Restangular

I have just discovered the Angular "Restangular" library, and oh my it does look great! However, I am running into complications when performing some simple GET requests with it. I am just experimenting and attempting to return a list of JSON objects from one of my API routes. The GET request to the server is being made successfully, however when I attempt to bind the results to the $scope object and display them, I cannot seem to get it working. Please refer to my controller script where I initialise and use the getList() method to retrieve the list of objects.
angular.module("app")
.controller("BlogsController", function ($scope, $location, Restangular) {
var Blogs = Restangular.all("blogs");
$scope.list = function () {
$scope.blogs = Blogs.getList("blogs");
}
I have bind initialised the "list()" function on the HTML page, and am using the ng-repeat directive to loop through all the JSON objects. This is my HTML markup below:
<tr data-ng-repeat="blog in blogs">
<td>{{blog.title}}</td>
<td>{{blog.category}}</td>
<td>{{blog.author}}</td>
<td>{{blog.content}}</td>
<td>{{blog.createdOn}}</td>
</tr>
I am very new to the AngularJs framework, and would like to know if anyone knows the potential problem that I am being faced with. Please comment and let me know what needs to be fixed. All answers will be greatly appreciated, Thanks.
You have to use the $object property of the promise which getList() returns. This is where the values from the HTTP response will be filled in when the promise is resolved.
$scope.blogs = Blogs.getList("blogs").$object;
Another, more explicit and less "magical" way of fetching the values is to attach callbacks to the promise returned by getList(), which will be called when the promise is resolved:
Blogs.getList("blogs").then(function(blogs) {
// Success callback
$scope.blogs = blogs;
}, function(error) {
// Error callback
console.log("we had an error here ...");
});
var Blogs = Restangular.all("blogs").getList();
this is your promise, you have to resolve it with then() like this
Blogs.then(function(blogs){
$scope.blogs = blogs;
});

How to write services with $q and $http calls without being repetitive

I'm trying to figure out an elegant way to write AngularJS services without being so repetitive with the $q syntax.
Currently, I'm writing services like follows:
(function() {
function ServiceFactory($q, $timeout, $http) {
return {
getFoo: function() {
var deferred = $q.defer();
$timeout(function() {
$http.get('/the/foo/thing').then(function(response) {
if (response.isError) {
deferred.reject();
} else {
deferred.resolve(response.data.foo);
}
}, function() {
deferred.reject();
});
});
return deferred.promise;
}
};
}
angular.module('myapp').service('MyService', ['$q', '$timeout', '$http', ServiceFactory]);
}.call(this));
It works very well, buy I'm always writing down a bunch of code just to delay $http.get and expose a Promise. Sometimes I will have some extra stuff into success callback, like handling data, creating a different response object... But most of the time, is just like the code above: call $q.defer + $http.get().then... + return promise
So, I'm thinking about a way to clean up/reduce the code without affecting the clarity of what I'm doing, e.g if another developer open the file, it should not be a mystery.
As a side note, the $http service here is actually a decorator, handling server responses to give me a more structured object with things like response.isError and response.data.
Also, I have seen a similar solution in another question {1}, but this is not as same. Just returning the response of $http.get().then() will expose the entire response to controllers on response, and it is not the desired effect. Instead, when I call MyService.getFoo().then(...), I'm expecting a foo object as a response from service, fed by server via response.data.foo, or a call to errorCallback.
I've forgot to mention: my server is not RESTful, so $resource is not an option right now. My URLs are more like /thing/get/:id, /thing/do-stuff/:id.
{1} Similar question
After thinking for a while, I figured out a better way to write the services. I've done some changes to $http response, and now my implementation is returning a Promise whenever a Controller calls http.get() or whatever http methods. With this implementation, I've reduced the methods' code to two or three lines per request in most of the cases. Now I should try to use the AngularJS decorator setup.
A working example is here:
http://embed.plnkr.co/p4EHQAbE40XWXjBWqjIM/preview

Categories