Why move my $http calls into a service? - javascript

Currently I have calls like this all over my three controllers:
$scope.getCurrentUser = function () {
$http.post("/Account/CurrentUser", {}, postOptions)
.then(function(data) {
var result = angular.fromJson(data.data);
if (result != null) {
$scope.currentUser = result.id;
}
},
function(data) {
alert("Browser failed to get current user.");
});
};
I see lots of advice to encapsulate the $http calls into an HttpService, or some such, but that it is much better practice to return the promise than return the data. Yet if I return the promise, all but one line in my controller $http call changes, and all the logic of dealing with the response remains in my controllers, e.g:
$scope.getCurrentUser = function() {
RestService.post("/Account/CurrentUser", {}, postOptions)
.then(function(data) {
var result = angular.fromJson(data.data);
if (result != null) {
$scope.currentUser = result.id;
}
},
function(data) {
alert("Browser failed to get current user.");
});
};
I could create a RestService for each server side controller, but that would only end up calling a core service and passing the URL anyway.

There are a few reasons why it is good practice in non-trivial applications.
Using a single generic service and passing in the url and parameters doesn't add so much value as you noticed. Instead you would have one method for each type of fetch that you need to do.
Some benefits of using services:
Re-usability. In a simple app, there might be one data fetch for each controller. But that can soon change. For example, you might have a product list page with getProducts, and a detail page with getProductDetail. But then you want to add a sale page, a category page, or show related products on the detail page. These might all use the original getProducts (with appropriate parameters).
Testing. You want to be able to test the controller, in isolation from an external data source. Baking the data fetch in to the controller doesn't make that easy. With a service, you just mock the service and you can test the controller with stable, known data.
Maintainability. You may decide that with simple services, it's a similar amount of code to just put it all in the controller, even if you're reusing it. What happens if the back-end path changes? Now you need to update it everywhere it's used. What happens if some extra logic is needed to process the data, or you need to get some supplementary data with another call? With a service, you make the change in one place. With it baked in to controllers, you have more work to do.
Code clarity. You want your methods to do clear, specific things. The controller is responsible for the logic around a specific part of the application. Adding in the mechanics of fetching data confuses that. With a simple example the only extra logic you need is to decode the json. That's not bad if your back-end returns exactly the data your controllers need in exactly the right format, but that may not be the case. By splitting the code out, each method can do one thing well. Let the service get data and pass it on to the controller in exactly the right format, then let the controller do it's thing.

A controller carries out presentation logic (it acts as a viewmodel in Angular Model-View-Whatever pattern). Services do business logic (model). It is battle-proven separation of concerns and inherent part of OOP good practices.
Thin controllers and fat services guarantee that app units stay reusable, testable and maintainable.
There's no benefit in replacing $http with RestService if they are the same thing. The proper separation of business and presentation logic is expected to be something like this
$scope.getCurrentUser = function() {
return UserService.getCurrent()
.then(function(user) {
$scope.currentUser = user.id;
})
.catch(function(err) {
alert("Browser failed to get current user.");
throw err;
});
});
It takes care of result conditioning and returns a promise. getCurrentUser passes a promise, so it could be chained if needed (by other controller method or test).

It would make sense to have your service look like this:
app.factory('AccountService', function($http) {
return {
getCurrentUser: function(param1, param2) {
var postOptions = {}; // build the postOptions based on params here
return $http.post("/Account/CurrentUser", {}, postOptions)
.then(function(response) {
// do some common processing here
});
}
};
});
Then calling this method would look this way:
$scope.getCurrentUser = function() {
AccountService.getCurrentUser(param1, param2)
.then(function(currentUser){
// do your stuff here
});
};
Which looks much nicer and lets you avoid the repetition of the backend service url and postOptions variable construction in multiple controllers.

Simple. Write every function as a service so that you can reuse it. As this is an asynchronous call use angular promise to send the data back to controller by wrapping it up within a promise.

Related

How to handle $http asynchronous issues in angularjs factory service?

In the factory service js file, I created a service like this:
DashboardFactory.restService = function(method) {
var teststatus = "aaa";
switch (method) {
case "getAll":
$http.get($_REST_API_ROOT_URL + "clients").success(function(data) {
teststatus = data;
}).error(function(error) {
teststatus = 'Unable to load the client data: ' + error.message;
});
teststatus = "bbb";
break;
}
return teststatus;
};
In controller, the code is like this:
$scope.AllClients=DashboardFactory.restService("getAll","","");
I also put the "AllClients" on the html page to monitor the result:
{{AllClients}}
I think the "AllClients" should show the API data from the remote server. But in reality it always give me "bbb".
What should I do?
Thanks in advance!
teststatus gets set as "bbb" and is returned before the API call is finished since the call is asynchronous.
Refer to AngularJS $http call in a Service, return resolved data, not promises for the different ways you can return the data from your API call.
What you want to do is return teststatus at the end of your .success()/.error() functions rather than outside. That way, it only returns once it finishes the API call and sets it to the data returned by the call.
Method 1: You don't NEED to return it at all. You could send in your model to the service RestService.getAllClients = function(myModel), set a property on the model on success myModel.AllClients = data; and then display it with {{myModel.AllClients}}. Setting the property directly on the passed in model will update the binding automagically.
Example: http://jsbin.com/muxijozofa/edit?html,js,output
Method 2: Otherwise you'd need to return the entire get call, which will return a promise, which you'll then need to resolve on the controller as per f.e How do I return data from a $http.get() inside a factory in angularjs
Refactoring tip: Instead of building a "catch-all" rest service filled with switch-case:s, you could build a general rest-service factory which you then implement for each type of call. So you get one method per type of rest call. Instead of DashboardFactory.restService, you'd then call RestService.getAllClients, which sets the data or returns a promise per the methods above.
Switch-case is just bad for feature selection, you're hiding functionality and making the code prone to hidden errors.

Angular Best practice: promise in a Factory or in a Controller?

I have a basic factory in my app that handles API calls. Currently I'm using the form:
.factory('apiFactory', function($http){
var url = 'http://192.168.22.8:8001/api/v1/';
return {
getReports: function() {
return $http.get(url+'reports').then(function(result) {
return result;
});
},
getReport: function(id) {
return $http.get(url+'report/'+id).then(function(result) {
return result;
});
}
}
})
And in my controller I'm handling the promise like so:
.controller('exampleController', function($scope, apiFactory) {
apiFactory.getReports().then(
function(answer) {
if (answer.status==200){
if (answer.data.status == "error"){
// DISPLAY ERROR MESSAGE
console.log(answer.data.msg);
}
} else{
// THROW error
console.log('error: ', answer);
}
},
function(error){
console.log('error: ', answer);
}
);
}
}
})
It seems I could move the promise handling to my Factory instead of doing it in my controller, but I'm not sure if that would have any benefits others than a smaller controller.
Could somebody explain the best practices regarding this pattern?
It is ultimately up to you how much data you want to provide to the caller of the service. If needed, you could definitely return the HTTP response object to the caller, and have them process the response (which, btw, is always HTTP 2xx, if the promise is resolved rather than rejected).
But if you want to isolate the caller from the specifics of how the data got there (maybe it was cached, or supplied via another mechanism), and if you need to post-process the data, then it is advisable to handle the response in the service.
Here's an example:
.factory("apiService", function($http, $q){
var url = 'http://192.168.22.8:8001/api/v1/';
return {
getReports: function() {
return $http.get(url+'reports').then(function(result) {
var data = result.data;
if (data === "something I don't accept"){
return $q.reject("Invalid data");
}
var processedData = processData(data);
return processedData;
})
.catch(function(err){
// for example, "re-throw" to "hide" HTTP specifics
return $q.reject("Data not available");
})
},
// same idea for getReport
}
});
Then the controller wouldn't need to care about the underlying mechanism - all it gets is data or a rejection.
.controller('exampleController', function($scope, apiService) {
apiService.getReports()
.then(function(reports){
$scope.reports = reports; // actual reports data
});
})
Off-topic:
Notice how I changed the name of the service from "apiFactory" to "apiService". I wanted to point that out to remove a possible misconception. Whether you use .factory or .service or .value what you get as an injectable is always a service instance. .factory is just a mechanism of how this service is instantiated, so the name "apiFactory" is a misnomer. The only "factory" here is a function that you register with .factory (which could be anonymous, of course):
.factory("fooSvc", function fooSvcFactory(){
return {
getFoo: function(){...}
}
})
Better to keep all the data fetching inside the factory. This keeps the controller free from state, and it no longer cares how your factory works. If you change how you get data (e.g. not using $http) your controller shouldn't care, as it just calls getReport() and
A good explanation (see "Resolving Model data, no callback arg binding in Controllers"):
http://toddmotto.com/rethinking-angular-js-controllers/
Short answer: Handle promises in Factory.
Why?
Problems you face if you handle promises in Controller:
Let's say you have 5 Controllers that use the same Factory. Now let's say that you want to handle the errors when the promise does not get resolved correctly. So in the first controller, you write an error callback (or the catch(exception) more precisely, as you are dealing with promises), that shows you an alert message with the error. When the promise fails, this controller shows an alert with the error message. So far, so good? Right. But wait! What about the other 4 controllers? You haven't handled the errors in them. So now you end up copying the error handling code from the first controller & pasting it in the rest of the 4 controllers.
Now the fun starts. Imagine that you want to change your logic in the error state. Maybe you want to just log the error in console, or show a toaster message perhaps. So you go to the first controller & update the code. You think you are done? NO!!! There are 4 other controllers that are showing the alert message (remember that you pasted the code from the first controller previously???). So now, you try to update the rest of the controllers by pasting the new error message logic. Think about all the controllers that you need to update, if you have more!!! Tiring, isn't it???
Why to resolve promise in Factory:
Now let's say that you write your error handling logic in your service and all the controllers are using this service. You have written code to show an alert in case of a promise that fails in this service. All controllers will now start showing this alert, in case of a broken promise. In future, if you wish to update your error logic, you simply update this error logic in your service & all your controllers will start using this updated logic automatically, without any more effort on your part. This way, you have saved yourself tons of time.
So think about this:
1 change in Factory vs 1 change in all Controllers (which maybe 1 or more)
I would definitely vouch for 1 change in Factory as that helps reuse my logic with just a simple change and only once. All my controllers will start using the new logic. Controllers are supposed to be slim, lean & clean. Factories & Services are supposed to be reusable. For this reason of reusability, I strongly suggest you handle promise in Factory/Service.

Which things i can put in service in angular Js

I have read many articles but still could not find how to put stuff in services.
Currently this is my Service
angular
.module('users')
.factory('objectiveService', objectiveService);
objectiveService.$inject = ['$http', 'Restangular'];
function objectiveService($http, Restangular) {
return {
getObjectives: getObjectives,
getSingleObjective: getSingleObjective
};
function getObjectives(pid) {
var pr = Restangular
.all('api')
.all('users')
.one('subjects', pid)
.all('objectives');
return pr;
}
function getSingleObjective(oid) {
var pr = Restangular
.all('api')
.all('users')
.one('objectives', oid);
return pr
}
}
This is the controller:
var _vm = this;
this.objPromise = objectiveService.getObjectives(44);
function getData() {
var promise = _vm.objPromise;
promise
.getList(filters)
.then(function(result) {
$scope.gridData = result;
});
}
function remove(id) {
if (confirm('Are you sure you want to delete this!')) {
objectiveService.getSingleObjective(id).remove().then(function() {
$scope.getData();
});
}
}
// initial call
$scope.getData();
In this code i basically see no use to define service because I still has to use then() in the controller to assign data to Grid.
Also I can't use then() in service because there I don't have $scope to update the data.
People say to do all stuff in service but I am not able to figure out how.
Ideally I want to put all functions like remove(object_id) in service
IS it possible that I can just do objectiveService.remove(id) in controller.
But then I have to call $scope.getData() after deleting which I can't do in service
The reason to use services is not to avoid the use of .then. In your controller you would use .then like so:
objectiveService.getObjectives(filters)
.then(function(result) {
$scope.gridData = result;
});
.then is required as the call to get data occurs asynchronously.
Some reasons taken from John Papa's style guide:
The controller's responsibility is for the presentation and gathering of information for the view. It should not care how it gets the data, just that it knows who to ask for it.
This makes it easier to test (mock or real) the data calls when testing a controller that uses a data service.
Data service implementation may have very specific code to handle the data repository. This may include headers, how to talk to the data, or other services such as $http. Separating the logic into a data service encapsulates this logic in a single place hiding the implementation from the outside consumers (perhaps a controller), also making it easier to change the implementation.
If you put the code in the controller it also means it's not reusable. You can't use it in more than one controller or you can't you it in another service.

How to store/communicate global asynchronous data to controllers

The basic premise is this....
I have an application. When the user hits the application, it immediately fetches various information regarding the user from a sharepoint server through an ajax call. And depending on what kind of data is received from the user, the app has to display/hide certain information and set certain settings.
Each controller within the application is heavily dependent on the data that is returned from this sharepoint server.
I have several questions...
First, where should this ajax call be made? Ideally it should be run as soon as possible, so should it be executed in the app.run()?
Second, where should this data that gets returned from the sharepoint server be stored? I read that making a factory for the sole purpose of storing data is not best practice, and it is better to just use the $rootscope. Right now, I am just storing a User object in a factory call "User" which in hindsight I guess is a no no
Finally, I'm not sure if there is a way to suspend the loading of the controllers as they are heavily dependent on this on the data that gets returned, but if there isn't, how would one communicate the information that gets received to the controllers. Would this be a case to use the $broadcast method?
Right now I have a kind of hackish solution. It gets the job done, but I'm pretty sure it is less than ideal
Here is a part of one controller. I am injecting the factory User into it
if (User.HasLoadedUserProps == false)
{
User.registerObserverCallback(hasLoadedProperties);
User.GetUser("1111");
}
else
{
if (User.IsAdmin == true)
//do whatever
}
Once the necessary information has been returned from the ajax call, it calls this
var hasLoadedProperties = function ()
{
if (User.IsAdmin == true)
//do whatever
else
utilities.popupBox("You do not have permission to view this page", "Access Denied");
}
Any wisdom, insight, or advice is appreciated!
First:
When your ajax call should happen depends on a few things, but since you mention that you'd like to defer controller loading until the user data is pulled down, your best bet is to put the call in your service. More on that in my response to your last item. Placing that data in a service also makes it easier to share across controllers, which brings us to the next point...
Second:
Your user data absolutely should go in a service, and absolutely should not go in $rootScope. Think of $rootScope like you do window / globals in JavaScript. You want to avoid using it for much of anything. An exception would be where you really, really need to use events ($broadcast/$emit/$on) but even those cases should be rare.
Finally:
Look into the resolve option for $routeProvider (there are similar options for ui-router if you prefer that route (no pun intended).
This option allows you to defer the instantiation of a controller until a set of promises is resolved. In your case, you should return a promise from your User service, which is resolved once the user data is retrieved.
To help demonstrate these points, I made this simple demo. This code, along with the links to the Angular docs, should be enough to get you going ...
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'beer.html',
controller: 'BeerController',
resolve: {
beer: function(Beer){ //injected into controller once promise is resolved
return Beer.getFavorite();
}
}
})
})
.controller('BeerController', function($scope, beer) { // Will load after 3s
$scope.favoriteBeer = beer; // beer comes from resolve in $routeProvider
})
.factory('Beer', function($timeout) {
var beer = {
favorite: 'porter'
};
beer.getFavorite = function() {
return $timeout(function() { // pretend this is an ajax call
return beer.favorite;
}, 3000);
}
return beer;
});
...where beer.html contains:
<div>
My favorite kind of beer is: {{favoriteBeer}}
</div>

AngularJS pass data from controller to another controller

What I have done.
I retrieve a list of videos from youtube api with json in a controllerA with specific directive. The json contain the list of video and the video itself details.
What I want to do.
When click on a video, I want the video's details display in another ng-view with other controllerB with using the json data I request before.
So my question is
How to pass the data from controllerA to controllerB
Note - $http service is use in controllerA
This is one of the common doubts when starting with AngularJS. By your requirement, I believe your best option is to create a service that retrieves the movie list and then use this service in both controllerA and controllerB.
module.factory('youtube', function() {
var movieListCache;
function getMovies(ignoreCache) {
if (ignoreCache || !movieListCache) {
movieListCache = $http...;
}
return movieListCache;
}
return {
get: getMovies
};
});
Then you just inject this service in both controllers.
module.controller('controllerA', ['youtube', function(youtube) {
youtube.get().then(function doSomethingAfterRetrievingTheMovies() {
});
}]);
module.controller('controllerB', ['youtube', function(youtube) {
youtube.get().then(function doAnotherThingAfterRetrievingTheMovies() {
});
}]);
If you need controllerA to manipulate the info before you use it in B then you could create more methods in the service. Something like this:
module.factory('youtube', function($q) {
var movieListCache,
deferred = $q.defer();
function getMovies(ignoreCache) {
if (ignoreCache || !movieListCache) {
movieListCache = $http...;
}
return movieListCache;
}
function getChangedMovies() {
return deferred.promise;
}
function setChangedMovies(movies) {
deferred.resolve(movies);
}
return {
get: getMovies,
getChanged: getChangedMovies,
setChanged: setChangedMovies
};
});
If you don't know what $q is, take a look at the docs. This is mandatory to handle async operations.
Anyway, there are some other ways of accomplishing this task too:
You could save the videos at $rootScope
If the controllers are father and son the you could use require to retrieve each other controller
IMHO, #1 is a generic solution; I'd use it only if there is no other option. And #2 is useful if you have an intrinsic need to communicate between these controllers, such as configuring or letting one know about the other's existence. There is a example here.
What you want to do is to share stateful singleton information; therefore, a service is the way to go.

Categories

Resources